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 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-03-05 11:55:50 +01:00
commit 4950feb751

View file

@ -2,37 +2,45 @@ package no.naiv.tiltshift.util
import android.content.Context import android.content.Context
import android.os.VibrationEffect import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager import android.os.VibratorManager
import android.util.Log
/** /**
* Provides haptic feedback for user interactions. * Provides haptic feedback for user interactions.
* Gracefully degrades on devices without vibration hardware.
*/ */
class HapticFeedback(private val context: Context) { class HapticFeedback(private val context: Context) {
private val vibrator by lazy { companion object {
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager private const val TAG = "HapticFeedback"
vibratorManager.defaultVibrator }
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). * Light tick for UI feedback (button press, slider change).
*/ */
fun tick() { fun tick() {
vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)) vibrateOrLog(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
} }
/** /**
* Click feedback for confirmations. * Click feedback for confirmations.
*/ */
fun click() { fun click() {
vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)) vibrateOrLog(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
} }
/** /**
* Heavy click for important actions (photo capture). * Heavy click for important actions (photo capture).
*/ */
fun heavyClick() { 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() { fun success() {
val timings = longArrayOf(0, 30, 50, 30) val timings = longArrayOf(0, 30, 50, 30)
val amplitudes = intArrayOf(0, 100, 0, 200) 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() { fun error() {
val timings = longArrayOf(0, 50, 30, 50, 30, 50) val timings = longArrayOf(0, 50, 30, 50, 30, 50)
val amplitudes = intArrayOf(0, 150, 0, 150, 0, 150) 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)
}
} }
} }