• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 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.server.vibrator;
18 
19 import static android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.res.Resources;
24 import android.os.VibrationAttributes;
25 import android.os.VibrationEffect;
26 import android.os.VibratorInfo;
27 import android.view.HapticFeedbackConstants;
28 import android.view.InputDevice;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.io.PrintWriter;
33 
34 /**
35  * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback.
36  */
37 public final class HapticFeedbackVibrationProvider {
38     private static final String TAG = "HapticFeedbackVibrationProvider";
39 
40     private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
41             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
42     private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES =
43             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION);
44     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
45             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
46     private static final VibrationAttributes COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES =
47             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
48     private static final VibrationAttributes IME_FEEDBACK_VIBRATION_ATTRIBUTES =
49             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_IME_FEEDBACK);
50 
51     private final VibratorInfo mVibratorInfo;
52     private final boolean mHapticTextHandleEnabled;
53     // Vibrator effect for haptic feedback during boot when safe mode is enabled.
54     private final VibrationEffect mSafeModeEnabledVibrationEffect;
55 
56     private final HapticFeedbackCustomization mHapticFeedbackCustomization;
57 
58     private float mKeyboardVibrationFixedAmplitude;
59 
HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo)60     public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
61         this(res, vibratorInfo, new HapticFeedbackCustomization(res, vibratorInfo));
62     }
63 
64     @VisibleForTesting
HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo, HapticFeedbackCustomization hapticFeedbackCustomization)65     HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo,
66             HapticFeedbackCustomization hapticFeedbackCustomization) {
67         mVibratorInfo = vibratorInfo;
68         mHapticTextHandleEnabled = res.getBoolean(
69                 com.android.internal.R.bool.config_enableHapticTextHandle);
70         mHapticFeedbackCustomization = hapticFeedbackCustomization;
71 
72         VibrationEffect safeModeVibration = mHapticFeedbackCustomization.getEffect(
73                 HapticFeedbackConstants.SAFE_MODE_ENABLED);
74         mSafeModeEnabledVibrationEffect = safeModeVibration != null ? safeModeVibration
75                 : VibrationSettings.createEffectFromResource(res,
76                         com.android.internal.R.array.config_safeModeEnabledVibePattern);
77 
78         mKeyboardVibrationFixedAmplitude = res.getFloat(
79                 com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude);
80         if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) {
81             mKeyboardVibrationFixedAmplitude = -1;
82         }
83     }
84 
85     /**
86      * Provides the {@link VibrationEffect} for a given haptic feedback effect ID (provided in
87      * {@link HapticFeedbackConstants}).
88      *
89      * @param effectId the haptic feedback effect ID whose respective vibration we want to get.
90      * @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
91      *          the provided effect ID is not supported.
92      */
getVibration(int effectId)93     @Nullable public VibrationEffect getVibration(int effectId) {
94         if (!isFeedbackConstantEnabled(effectId)) {
95             return null;
96         }
97         VibrationEffect customizedVibration = mHapticFeedbackCustomization.getEffect(effectId);
98         if (customizedVibration != null) {
99             return customizedVibration;
100         }
101         return getVibrationForHapticFeedback(effectId);
102     }
103 
104     /**
105      * Provides the {@link VibrationEffect} for a given haptic feedback effect ID (provided in
106      * {@link HapticFeedbackConstants}).
107      *
108      * @param effectId    the haptic feedback effect ID whose respective vibration we want to get.
109      * @param inputSource the {@link InputDevice.Source} that customizes the haptic feedback
110      *                    corresponding to the {@code effectId}.
111      * @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
112      * the provided effect ID is not supported.
113      */
getVibration(int effectId, int inputSource)114     @Nullable public VibrationEffect getVibration(int effectId, int inputSource) {
115         if (!isFeedbackConstantEnabled(effectId)) {
116             return null;
117         }
118         VibrationEffect customizedVibration = mHapticFeedbackCustomization.getEffect(effectId,
119                 inputSource);
120         if (customizedVibration != null) {
121             return customizedVibration;
122         }
123         return getVibrationForHapticFeedback(effectId);
124     }
125 
126     /**
127      * Provides the {@link VibrationAttributes} that should be used for a haptic feedback.
128      *
129      * @param effectId the haptic feedback effect ID whose respective vibration attributes we want
130      *      to get.
131      * @param flags Additional flags as per {@link HapticFeedbackConstants}.
132      * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
133      * @return the {@link VibrationAttributes} that should be used for the provided haptic feedback.
134      */
getVibrationAttributes(int effectId, @HapticFeedbackConstants.Flags int flags, @HapticFeedbackConstants.PrivateFlags int privFlags)135     public VibrationAttributes getVibrationAttributes(int effectId,
136             @HapticFeedbackConstants.Flags int flags,
137             @HapticFeedbackConstants.PrivateFlags int privFlags) {
138         VibrationAttributes attrs;
139         switch (effectId) {
140             case HapticFeedbackConstants.EDGE_SQUEEZE:
141             case HapticFeedbackConstants.EDGE_RELEASE:
142                 attrs = PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES;
143                 break;
144             case HapticFeedbackConstants.ASSISTANT_BUTTON:
145             case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
146                 attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
147                 break;
148             case HapticFeedbackConstants.SCROLL_TICK:
149             case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
150             case HapticFeedbackConstants.SCROLL_LIMIT:
151                 // TODO(b/372820923): use touch attributes by default.
152                 attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
153                 break;
154             case HapticFeedbackConstants.KEYBOARD_TAP:
155             case HapticFeedbackConstants.KEYBOARD_RELEASE:
156                 attrs = createKeyboardVibrationAttributes(privFlags);
157                 break;
158             case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
159             case HapticFeedbackConstants.BIOMETRIC_REJECT:
160                 attrs = COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES;
161                 break;
162             default:
163                 attrs = TOUCH_VIBRATION_ATTRIBUTES;
164         }
165         return getVibrationAttributesWithFlags(attrs, effectId, flags);
166     }
167 
168     /**
169      * Similar to {@link #getVibrationAttributes(int, int, int)} but also handles
170      * input source customization.
171      *
172      * @param inputSource the {@link InputDevice.Source} that customizes the
173      *                    {@link VibrationAttributes}.
174      */
getVibrationAttributes(int effectId, int inputSource, @HapticFeedbackConstants.Flags int flags, @HapticFeedbackConstants.PrivateFlags int privFlags)175     public VibrationAttributes getVibrationAttributes(int effectId,
176             int inputSource,
177             @HapticFeedbackConstants.Flags int flags,
178             @HapticFeedbackConstants.PrivateFlags int privFlags) {
179         if (hapticFeedbackInputSourceCustomizationEnabled()) {
180             switch (effectId) {
181                 case HapticFeedbackConstants.SCROLL_TICK,
182                         HapticFeedbackConstants.SCROLL_ITEM_FOCUS,
183                         HapticFeedbackConstants.SCROLL_LIMIT -> {
184                     VibrationAttributes attrs = inputSource == InputDevice.SOURCE_ROTARY_ENCODER
185                             ? HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES
186                             : TOUCH_VIBRATION_ATTRIBUTES;
187                     return getVibrationAttributesWithFlags(attrs, effectId, flags);
188                 }
189             }
190         }
191         return getVibrationAttributes(effectId, flags, privFlags);
192     }
193 
194     /**
195      * Returns true if given haptic feedback is restricted to system apps with permission
196      * {@code android.permission.VIBRATE_SYSTEM_CONSTANTS}.
197      *
198      * @param effectId the haptic feedback effect ID to check.
199      * @return true if the haptic feedback is restricted, false otherwise.
200      */
isRestrictedHapticFeedback(int effectId)201     public boolean isRestrictedHapticFeedback(int effectId) {
202         switch (effectId) {
203             case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
204             case HapticFeedbackConstants.BIOMETRIC_REJECT:
205                 return true;
206             default:
207                 return false;
208         }
209     }
210 
211     /** Dumps relevant state. */
dump(String prefix, PrintWriter pw)212     public void dump(String prefix, PrintWriter pw) {
213         pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
214     }
215 
isFeedbackConstantEnabled(int effectId)216     private boolean isFeedbackConstantEnabled(int effectId) {
217         return switch (effectId) {
218             case HapticFeedbackConstants.TEXT_HANDLE_MOVE -> mHapticTextHandleEnabled;
219             case HapticFeedbackConstants.NO_HAPTICS -> false;
220             default -> true;
221         };
222     }
223 
224     /**
225      * Get {@link VibrationEffect} respective {@code effectId} from platform-wise mapping. This
226      * method doesn't include OEM customizations.
227      */
228     @Nullable
getVibrationForHapticFeedback(int effectId)229     private VibrationEffect getVibrationForHapticFeedback(int effectId) {
230         switch (effectId) {
231             case HapticFeedbackConstants.CONTEXT_CLICK:
232             case HapticFeedbackConstants.GESTURE_END:
233             case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
234             case HapticFeedbackConstants.SCROLL_TICK:
235             case HapticFeedbackConstants.SEGMENT_TICK:
236                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
237 
238             case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
239             case HapticFeedbackConstants.CLOCK_TICK:
240             case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
241                 return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
242 
243             case HapticFeedbackConstants.KEYBOARD_RELEASE:
244             case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
245                 // keyboard effect is not customized by the input source.
246                 return getKeyboardVibration(effectId);
247 
248             case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
249             case HapticFeedbackConstants.DRAG_CROSSING:
250                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK, /* fallback= */ false);
251 
252             case HapticFeedbackConstants.VIRTUAL_KEY:
253             case HapticFeedbackConstants.EDGE_RELEASE:
254             case HapticFeedbackConstants.CALENDAR_DATE:
255             case HapticFeedbackConstants.CONFIRM:
256             case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
257             case HapticFeedbackConstants.GESTURE_START:
258             case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
259             case HapticFeedbackConstants.SCROLL_LIMIT:
260                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
261 
262             case HapticFeedbackConstants.LONG_PRESS:
263             case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
264             case HapticFeedbackConstants.DRAG_START:
265             case HapticFeedbackConstants.EDGE_SQUEEZE:
266                 return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
267 
268             case HapticFeedbackConstants.REJECT:
269             case HapticFeedbackConstants.BIOMETRIC_REJECT:
270                 return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
271 
272             case HapticFeedbackConstants.SAFE_MODE_ENABLED:
273                 // safe mode effect is not customized by the input source.
274                 return mSafeModeEnabledVibrationEffect;
275 
276             case HapticFeedbackConstants.ASSISTANT_BUTTON:
277                 // assistant effect is not customized by the input source.
278                 return getAssistantButtonVibration();
279 
280             case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
281                 return getVibration(
282                         VibrationEffect.Composition.PRIMITIVE_TICK,
283                         /* primitiveScale= */ 0.4f,
284                         VibrationEffect.EFFECT_TEXTURE_TICK);
285 
286             case HapticFeedbackConstants.TOGGLE_ON:
287                 return getVibration(
288                         VibrationEffect.Composition.PRIMITIVE_TICK,/* primitiveScale= */ 0.5f,
289                         VibrationEffect.EFFECT_TICK);
290 
291             case HapticFeedbackConstants.TOGGLE_OFF:
292                 return getVibration(
293                         VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
294                         /* primitiveScale= */ 0.2f,
295                         VibrationEffect.EFFECT_TEXTURE_TICK);
296 
297             case HapticFeedbackConstants.NO_HAPTICS:
298             default:
299                 return null;
300         }
301     }
302 
303     @NonNull
getVibration(int primitiveId, float primitiveScale, int predefinedVibrationEffectId)304     private VibrationEffect getVibration(int primitiveId, float primitiveScale,
305             int predefinedVibrationEffectId) {
306         if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
307             return VibrationEffect.startComposition()
308                     .addPrimitive(primitiveId, primitiveScale)
309                     .compose();
310         }
311         return VibrationEffect.get(predefinedVibrationEffectId);
312     }
313 
314     @NonNull
getAssistantButtonVibration()315     private VibrationEffect getAssistantButtonVibration() {
316         if (mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
317                 && mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)) {
318             // quiet ramp, short pause, then sharp tick
319             return VibrationEffect.startComposition()
320                     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
321                     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
322                     .compose();
323         }
324         // fallback for devices without composition support
325         return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
326     }
327 
328     @NonNull
getKeyboardVibration(int effectId)329     private VibrationEffect getKeyboardVibration(int effectId) {
330         int primitiveId;
331         int predefinedEffectId;
332         boolean predefinedEffectFallback;
333 
334         switch (effectId) {
335             case HapticFeedbackConstants.KEYBOARD_RELEASE:
336                 primitiveId = VibrationEffect.Composition.PRIMITIVE_TICK;
337                 predefinedEffectId = VibrationEffect.EFFECT_TICK;
338                 predefinedEffectFallback = false;
339                 break;
340             case HapticFeedbackConstants.KEYBOARD_TAP:
341             default:
342                 primitiveId = VibrationEffect.Composition.PRIMITIVE_CLICK;
343                 predefinedEffectId = VibrationEffect.EFFECT_CLICK;
344                 predefinedEffectFallback = true;
345         }
346         if (mKeyboardVibrationFixedAmplitude > 0) {
347             if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
348                 return VibrationEffect.startComposition()
349                         .addPrimitive(primitiveId, mKeyboardVibrationFixedAmplitude)
350                         .compose();
351             }
352         }
353         return VibrationEffect.get(predefinedEffectId, predefinedEffectFallback);
354     }
355 
createKeyboardVibrationAttributes( @apticFeedbackConstants.PrivateFlags int privFlags)356     private VibrationAttributes createKeyboardVibrationAttributes(
357             @HapticFeedbackConstants.PrivateFlags int privFlags) {
358         // Use touch attribute when the haptic is not apply to IME.
359         if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) {
360             return TOUCH_VIBRATION_ATTRIBUTES;
361         }
362         return IME_FEEDBACK_VIBRATION_ATTRIBUTES;
363     }
364 
getVibrationAttributesWithFlags(VibrationAttributes attrs, int effectId, int flags)365     private VibrationAttributes getVibrationAttributesWithFlags(VibrationAttributes attrs,
366             int effectId, int flags) {
367         int vibFlags = 0;
368         if ((flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0) {
369             vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
370         }
371         if (shouldBypassInterruptionPolicy(effectId)) {
372             vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
373         }
374 
375         return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs)
376                 .setFlags(vibFlags).build();
377     }
378 
shouldBypassInterruptionPolicy(int effectId)379     private static boolean shouldBypassInterruptionPolicy(int effectId) {
380         switch (effectId) {
381             case HapticFeedbackConstants.SCROLL_TICK:
382             case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
383             case HapticFeedbackConstants.SCROLL_LIMIT:
384                 // The SCROLL_* constants should bypass interruption filter, so that scroll haptics
385                 // can play regardless of focus modes like DND. Guard this behavior by the feature
386                 // flag controlling the general scroll feedback APIs.
387                 return android.view.flags.Flags.scrollFeedbackApi();
388             default:
389                 return false;
390         }
391     }
392 }
393