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