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