1 /* 2 * Copyright (C) 2021 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 package com.android.quickstep.util; 17 18 import static android.os.VibrationEffect.createPredefined; 19 import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; 20 21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 22 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 23 24 import android.annotation.SuppressLint; 25 import android.annotation.TargetApi; 26 import android.content.ContentResolver; 27 import android.content.Context; 28 import android.database.ContentObserver; 29 import android.media.AudioAttributes; 30 import android.os.Build; 31 import android.os.VibrationEffect; 32 import android.os.Vibrator; 33 import android.provider.Settings; 34 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.util.MainThreadInitializedObject; 37 38 /** 39 * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary. 40 */ 41 @TargetApi(Build.VERSION_CODES.Q) 42 public class VibratorWrapper { 43 44 public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE = 45 new MainThreadInitializedObject<>(VibratorWrapper::new); 46 47 public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder() 48 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) 49 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 50 .build(); 51 52 public static final VibrationEffect EFFECT_CLICK = 53 createPredefined(VibrationEffect.EFFECT_CLICK); 54 public static final VibrationEffect EFFECT_TEXTURE_TICK = 55 VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK); 56 57 /** 58 * Haptic when entering overview. 59 */ 60 public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK; 61 62 private final Vibrator mVibrator; 63 private final boolean mHasVibrator; 64 65 private boolean mIsHapticFeedbackEnabled; 66 VibratorWrapper(Context context)67 public VibratorWrapper(Context context) { 68 mVibrator = context.getSystemService(Vibrator.class); 69 mHasVibrator = mVibrator.hasVibrator(); 70 if (mHasVibrator) { 71 final ContentResolver resolver = context.getContentResolver(); 72 mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver); 73 final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) { 74 @Override 75 public void onChange(boolean selfChange) { 76 mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver); 77 } 78 }; 79 resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED), 80 false /* notifyForDescendents */, observer); 81 } else { 82 mIsHapticFeedbackEnabled = false; 83 } 84 } 85 isHapticFeedbackEnabled(ContentResolver resolver)86 private boolean isHapticFeedbackEnabled(ContentResolver resolver) { 87 return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1; 88 } 89 90 /** Vibrates with the given effect if haptic feedback is available and enabled. */ vibrate(VibrationEffect vibrationEffect)91 public void vibrate(VibrationEffect vibrationEffect) { 92 if (mHasVibrator && mIsHapticFeedbackEnabled) { 93 UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS)); 94 } 95 } 96 97 /** 98 * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only 99 * vibrates if haptic feedback is available and enabled. 100 */ 101 @SuppressLint("NewApi") vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect)102 public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) { 103 if (mHasVibrator && mIsHapticFeedbackEnabled) { 104 UI_HELPER_EXECUTOR.execute(() -> { 105 if (Utilities.ATLEAST_R && primitiveId >= 0 106 && mVibrator.areAllPrimitivesSupported(primitiveId)) { 107 mVibrator.vibrate(VibrationEffect.startComposition() 108 .addPrimitive(primitiveId, primitiveScale) 109 .compose(), VIBRATION_ATTRS); 110 } else { 111 mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS); 112 } 113 }); 114 } 115 } 116 } 117