From 4950feb7512cc0706c22bbf2c85fd9fd8b735151 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Thu, 5 Mar 2026 11:55:50 +0100 Subject: [PATCH] Make HapticFeedback null-safe for devices without vibrator Use safe cast (as? VibratorManager) and wrap vibrate calls in try-catch to prevent crashes on emulators, custom ROMs, or devices without vibration hardware. Co-Authored-By: Claude Opus 4.6 --- .../no/naiv/tiltshift/util/HapticFeedback.kt | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/no/naiv/tiltshift/util/HapticFeedback.kt b/app/src/main/java/no/naiv/tiltshift/util/HapticFeedback.kt index 801b275..0eb119a 100644 --- a/app/src/main/java/no/naiv/tiltshift/util/HapticFeedback.kt +++ b/app/src/main/java/no/naiv/tiltshift/util/HapticFeedback.kt @@ -2,37 +2,45 @@ package no.naiv.tiltshift.util import android.content.Context import android.os.VibrationEffect +import android.os.Vibrator import android.os.VibratorManager +import android.util.Log /** * Provides haptic feedback for user interactions. + * Gracefully degrades on devices without vibration hardware. */ class HapticFeedback(private val context: Context) { - private val vibrator by lazy { - val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager - vibratorManager.defaultVibrator + companion object { + private const val TAG = "HapticFeedback" + } + + private val vibrator: Vibrator? by lazy { + val vibratorManager = + context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as? VibratorManager + vibratorManager?.defaultVibrator } /** * Light tick for UI feedback (button press, slider change). */ fun tick() { - vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)) + vibrateOrLog(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)) } /** * Click feedback for confirmations. */ fun click() { - vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)) + vibrateOrLog(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)) } /** * Heavy click for important actions (photo capture). */ fun heavyClick() { - vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK)) + vibrateOrLog(VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK)) } /** @@ -41,7 +49,7 @@ class HapticFeedback(private val context: Context) { fun success() { val timings = longArrayOf(0, 30, 50, 30) val amplitudes = intArrayOf(0, 100, 0, 200) - vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1)) + vibrateOrLog(VibrationEffect.createWaveform(timings, amplitudes, -1)) } /** @@ -50,6 +58,14 @@ class HapticFeedback(private val context: Context) { fun error() { val timings = longArrayOf(0, 50, 30, 50, 30, 50) val amplitudes = intArrayOf(0, 150, 0, 150, 0, 150) - vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1)) + vibrateOrLog(VibrationEffect.createWaveform(timings, amplitudes, -1)) + } + + private fun vibrateOrLog(effect: VibrationEffect) { + try { + vibrator?.vibrate(effect) + } catch (e: Exception) { + Log.w(TAG, "Haptic feedback failed", e) + } } }