• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quickstep.util
18 
19 import android.content.Context
20 import android.os.VibrationEffect
21 import android.os.VibrationEffect.Composition
22 import android.os.Vibrator
23 import com.android.launcher3.dagger.ApplicationContext
24 import com.android.launcher3.dagger.LauncherAppSingleton
25 import com.android.launcher3.util.DaggerSingletonObject
26 import com.android.launcher3.util.VibratorWrapper
27 import com.android.quickstep.DeviceConfigWrapper.Companion.get
28 import com.android.quickstep.dagger.QuickstepBaseAppComponent
29 import javax.inject.Inject
30 import kotlin.math.pow
31 
32 /** Manages haptics relating to Contextual Search invocations. */
33 @LauncherAppSingleton
34 class ContextualSearchHapticManager
35 @Inject
36 internal constructor(
37     @ApplicationContext private val context: Context,
38     private val contextualSearchStateManager: ContextualSearchStateManager,
39     private val vibratorWrapper: VibratorWrapper,
40 ) {
41 
42     private var searchEffect = createSearchEffect()
43 
createSearchEffectnull44     private fun createSearchEffect() =
45         if (
46             context
47                 .getSystemService(Vibrator::class.java)!!
48                 .areAllPrimitivesSupported(Composition.PRIMITIVE_TICK)
49         ) {
50             VibrationEffect.startComposition()
51                 .addPrimitive(Composition.PRIMITIVE_TICK, 1f)
52                 .compose()
53         } else {
54             // fallback for devices without composition support
55             VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK)
56         }
57 
58     /** Indicates that search has been invoked. */
vibrateForSearchnull59     fun vibrateForSearch() {
60         searchEffect.let { vibratorWrapper.vibrate(it) }
61     }
62 
63     /** Indicates that search will be invoked if the current gesture is maintained. */
vibrateForSearchHintnull64     fun vibrateForSearchHint() {
65         val navbarConfig = get()
66         // Whether we should play the hint (ramp up) haptic
67         val shouldVibrate: Boolean =
68             if (
69                 context
70                     .getSystemService(Vibrator::class.java)!!
71                     .areAllPrimitivesSupported(Composition.PRIMITIVE_LOW_TICK)
72             ) {
73                 if (contextualSearchStateManager.shouldPlayHapticOverride.isPresent) {
74                     contextualSearchStateManager.shouldPlayHapticOverride.get()
75                 } else {
76                     navbarConfig.enableSearchHapticHint
77                 }
78             } else {
79                 false
80             }
81 
82         if (shouldVibrate) {
83             val startScale = navbarConfig.lpnhHapticHintStartScalePercent / 100f
84             val endScale = navbarConfig.lpnhHapticHintEndScalePercent / 100f
85             val scaleExponent = navbarConfig.lpnhHapticHintScaleExponent
86             val iterations = navbarConfig.lpnhHapticHintIterations
87             val delayMs = navbarConfig.lpnhHapticHintDelay
88             val composition = VibrationEffect.startComposition()
89             for (i in 0 until iterations) {
90                 val t = i / (iterations - 1f)
91                 val scale =
92                     ((1 - t) * startScale + t * endScale)
93                         .toDouble()
94                         .pow(scaleExponent.toDouble())
95                         .toFloat()
96                 if (i == 0) {
97                     // Adds a delay before the ramp starts
98                     composition.addPrimitive(Composition.PRIMITIVE_LOW_TICK, scale, delayMs)
99                 } else {
100                     composition.addPrimitive(Composition.PRIMITIVE_LOW_TICK, scale)
101                 }
102             }
103             vibratorWrapper.vibrate(composition.compose())
104         }
105     }
106 
107     companion object {
108         @JvmField
109         val INSTANCE =
110             DaggerSingletonObject(QuickstepBaseAppComponent::getContextualSearchHapticManager)
111     }
112 }
113