1 /*
2  * Copyright (C) 2018 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.systemui.statusbar;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.media.AudioAttributes;
22 import android.os.Process;
23 import android.os.VibrationAttributes;
24 import android.os.VibrationEffect;
25 import android.os.Vibrator;
26 import android.view.View;
27 
28 import androidx.annotation.VisibleForTesting;
29 
30 import com.android.systemui.dagger.SysUISingleton;
31 
32 import org.jetbrains.annotations.NotNull;
33 
34 import java.util.concurrent.Executor;
35 import java.util.concurrent.Executors;
36 
37 import javax.inject.Inject;
38 
39 /**
40  * A Helper class that offloads {@link Vibrator} calls to a different thread.
41  * {@link Vibrator} makes blocking calls that may cause SysUI to ANR.
42  * TODO(b/245528624): Use regular Vibrator instance once new APIs are available.
43  */
44 @SysUISingleton
45 public class VibratorHelper {
46 
47     private final Vibrator mVibrator;
48     public static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
49             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
50 
51     private static final VibrationEffect BIOMETRIC_SUCCESS_VIBRATION_EFFECT =
52             VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
53     private static final VibrationEffect BIOMETRIC_ERROR_VIBRATION_EFFECT =
54             VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
55     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
56             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
57 
58     private final Executor mExecutor;
59 
60     /**
61      * Creates a vibrator helper on a new single threaded {@link Executor}.
62      */
63     @Inject
VibratorHelper(@ullable Vibrator vibrator)64     public VibratorHelper(@Nullable Vibrator vibrator) {
65         this(vibrator, Executors.newSingleThreadExecutor());
66     }
67 
68     /**
69      * Creates new vibrator helper on a specific {@link Executor}.
70      */
71     @VisibleForTesting
VibratorHelper(@ullable Vibrator vibrator, Executor executor)72     public VibratorHelper(@Nullable Vibrator vibrator, Executor executor) {
73         mExecutor = executor;
74         mVibrator = vibrator;
75     }
76 
77     /**
78      * @see Vibrator#vibrate(long)
79      */
vibrate(final int effectId)80     public void vibrate(final int effectId) {
81         if (!hasVibrator()) {
82             return;
83         }
84         mExecutor.execute(() ->
85                 mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */),
86                         TOUCH_VIBRATION_ATTRIBUTES));
87     }
88 
89     /**
90      * @see Vibrator#vibrate(int, String, VibrationEffect, String, VibrationAttributes)
91      */
vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason, @NonNull VibrationAttributes attributes)92     public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe,
93             String reason, @NonNull VibrationAttributes attributes) {
94         if (!hasVibrator()) {
95             return;
96         }
97         mExecutor.execute(() -> mVibrator.vibrate(uid, opPkg, vibe, reason, attributes));
98     }
99 
100     /**
101      * @see Vibrator#vibrate(VibrationEffect, AudioAttributes)
102      */
vibrate(@onNull VibrationEffect effect, @NonNull AudioAttributes attributes)103     public void vibrate(@NonNull VibrationEffect effect, @NonNull AudioAttributes attributes) {
104         if (!hasVibrator()) {
105             return;
106         }
107         mExecutor.execute(() -> mVibrator.vibrate(effect, attributes));
108     }
109 
110     /**
111      * @see Vibrator#vibrate(VibrationEffect)
112      */
vibrate(@otNull VibrationEffect effect)113     public void vibrate(@NotNull VibrationEffect effect) {
114         if (!hasVibrator()) {
115             return;
116         }
117         mExecutor.execute(() -> mVibrator.vibrate(effect));
118     }
119 
120     /**
121      * @see Vibrator#hasVibrator()
122      */
hasVibrator()123     public boolean hasVibrator() {
124         return mVibrator != null && mVibrator.hasVibrator();
125     }
126 
127     /**
128      * @see Vibrator#cancel()
129      */
cancel()130     public void cancel() {
131         if (!hasVibrator()) {
132             return;
133         }
134         mExecutor.execute(mVibrator::cancel);
135     }
136 
137     /**
138      * Perform vibration when biometric authentication success
139      */
vibrateAuthSuccess(String reason)140     public void vibrateAuthSuccess(String reason) {
141         vibrate(Process.myUid(),
142                 "com.android.systemui",
143                 BIOMETRIC_SUCCESS_VIBRATION_EFFECT, reason,
144                 HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
145     }
146 
147     /**
148      * Perform vibration when biometric authentication error
149      */
vibrateAuthError(String reason)150     public void vibrateAuthError(String reason) {
151         vibrate(Process.myUid(), "com.android.systemui",
152                 BIOMETRIC_ERROR_VIBRATION_EFFECT, reason,
153                 HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
154     }
155 
156     /**
157      * Perform a vibration using a view and the one-way API with flags
158      * @see View#performHapticFeedback(int feedbackConstant, int flags)
159      */
performHapticFeedback(@onNull View view, int feedbackConstant, int flags)160     public void performHapticFeedback(@NonNull View view, int feedbackConstant, int flags) {
161         view.performHapticFeedback(feedbackConstant, flags);
162     }
163 
164     /**
165      * Perform a vibration using a view and the one-way API
166      * @see View#performHapticFeedback(int feedbackConstant)
167      */
performHapticFeedback(@onNull View view, int feedbackConstant)168     public void performHapticFeedback(@NonNull View view, int feedbackConstant) {
169         view.performHapticFeedback(feedbackConstant);
170     }
171 }
172