1 package com.android.systemui.unfold 2 3 import android.os.SystemProperties 4 import android.os.VibrationEffect 5 import android.os.Vibrator 6 import com.android.systemui.dagger.qualifiers.Main 7 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener 8 import com.android.systemui.unfold.updates.FoldProvider 9 import com.android.systemui.unfold.updates.FoldProvider.FoldCallback 10 import java.util.concurrent.Executor 11 import javax.inject.Inject 12 13 /** Class that plays a haptics effect during unfolding a foldable device */ 14 @SysUIUnfoldScope 15 class UnfoldHapticsPlayer 16 @Inject 17 constructor( 18 unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, 19 foldProvider: FoldProvider, 20 @Main private val mainExecutor: Executor, 21 private val vibrator: Vibrator? 22 ) : TransitionProgressListener { 23 24 private var isFirstAnimationAfterUnfold = false 25 26 init { 27 if (vibrator != null) { 28 // We don't need to remove the callback because we should listen to it 29 // the whole time when SystemUI process is alive 30 unfoldTransitionProgressProvider.addCallback(this) 31 } 32 33 foldProvider.registerCallback( 34 object : FoldCallback { onFoldUpdatednull35 override fun onFoldUpdated(isFolded: Boolean) { 36 if (isFolded) { 37 isFirstAnimationAfterUnfold = true 38 } 39 } 40 }, 41 mainExecutor 42 ) 43 } 44 45 private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN 46 onTransitionStartednull47 override fun onTransitionStarted() { 48 lastTransitionProgress = TRANSITION_PROGRESS_CLOSED 49 } 50 onTransitionProgressnull51 override fun onTransitionProgress(progress: Float) { 52 lastTransitionProgress = progress 53 } 54 onTransitionFinishingnull55 override fun onTransitionFinishing() { 56 // Run haptics only when unfolding the device (first animation after unfolding) 57 if (!isFirstAnimationAfterUnfold) { 58 return 59 } 60 61 isFirstAnimationAfterUnfold = false 62 63 // Run haptics only if the animation is long enough to notice 64 if (lastTransitionProgress < TRANSITION_NOTICEABLE_THRESHOLD) { 65 playHaptics() 66 } 67 } 68 onTransitionFinishednull69 override fun onTransitionFinished() { 70 lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN 71 } 72 playHapticsnull73 private fun playHaptics() { 74 vibrator?.vibrate(effect) 75 } 76 77 private val hapticsScale: Float 78 get() { 79 val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1") 80 return intensityString.toFloatOrNull() ?: 0.1f 81 } 82 83 private val hapticsScaleTick: Float 84 get() { 85 val intensityString = 86 SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6") 87 return intensityString.toFloatOrNull() ?: 0.6f 88 } 89 90 private val primitivesCount: Int 91 get() { 92 val count = SystemProperties.get("persist.unfold.primitives_count", "18") 93 return count.toIntOrNull() ?: 18 94 } 95 <lambda>null96 private val effect: VibrationEffect by lazy { 97 val composition = 98 VibrationEffect.startComposition() 99 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0F, 0) 100 101 repeat(primitivesCount) { 102 composition.addPrimitive( 103 VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 104 hapticsScale, 105 0 106 ) 107 } 108 109 composition 110 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, hapticsScaleTick) 111 .compose() 112 } 113 } 114 115 private const val TRANSITION_PROGRESS_CLOSED = 0f 116 private const val TRANSITION_PROGRESS_FULL_OPEN = 1f 117 private const val TRANSITION_NOTICEABLE_THRESHOLD = 0.9f 118