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 
17 package com.android.server.notification;
18 
19 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
20 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
21 
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.content.res.TypedArray;
26 import android.media.AudioAttributes;
27 import android.os.Process;
28 import android.os.VibrationAttributes;
29 import android.os.VibrationEffect;
30 import android.os.Vibrator;
31 import android.util.Slog;
32 
33 import com.android.internal.R;
34 import com.android.server.pm.PackageManagerService;
35 
36 import java.time.Duration;
37 import java.util.Arrays;
38 
39 /**
40  * NotificationManagerService helper for functionality related to the vibrator.
41  */
42 public final class VibratorHelper {
43     private static final String TAG = "NotificationVibratorHelper";
44 
45     private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
46     private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
47 
48     private final Vibrator mVibrator;
49     private final long[] mDefaultPattern;
50     private final long[] mFallbackPattern;
51     @Nullable private final float[] mDefaultPwlePattern;
52     @Nullable private final float[] mFallbackPwlePattern;
53     private final int mDefaultVibrationAmplitude;
54 
VibratorHelper(Context context)55     public VibratorHelper(Context context) {
56         mVibrator = context.getSystemService(Vibrator.class);
57         mDefaultPattern = getLongArray(context.getResources(),
58                 com.android.internal.R.array.config_defaultNotificationVibePattern,
59                 VIBRATE_PATTERN_MAXLEN,
60                 DEFAULT_VIBRATE_PATTERN);
61         mFallbackPattern = getLongArray(context.getResources(),
62                 R.array.config_notificationFallbackVibePattern,
63                 VIBRATE_PATTERN_MAXLEN,
64                 DEFAULT_VIBRATE_PATTERN);
65         mDefaultPwlePattern = getFloatArray(context.getResources(),
66                 com.android.internal.R.array.config_defaultNotificationVibeWaveform);
67         mFallbackPwlePattern = getFloatArray(context.getResources(),
68                 com.android.internal.R.array.config_notificationFallbackVibeWaveform);
69         mDefaultVibrationAmplitude = context.getResources().getInteger(
70             com.android.internal.R.integer.config_defaultVibrationAmplitude);
71     }
72 
73     /**
74      * Safely create a {@link VibrationEffect} from given vibration {@code pattern}.
75      *
76      * <p>This method returns {@code null} if the pattern is also {@code null} or invalid.
77      *
78      * @param pattern The off/on vibration pattern, where each item is a duration in milliseconds.
79      * @param insistent {@code true} if the vibration should loop until it is cancelled.
80      */
81     @Nullable
createWaveformVibration(@ullable long[] pattern, boolean insistent)82     public static VibrationEffect createWaveformVibration(@Nullable long[] pattern,
83             boolean insistent) {
84         try {
85             if (pattern != null) {
86                 return VibrationEffect.createWaveform(pattern, /* repeat= */ insistent ? 0 : -1);
87             }
88         } catch (IllegalArgumentException e) {
89             Slog.e(TAG, "Error creating vibration waveform with pattern: "
90                     + Arrays.toString(pattern));
91         }
92         return null;
93     }
94 
95     /**
96      * Safely create a {@link VibrationEffect} from given waveform description.
97      *
98      * <p>The waveform is described by a sequence of values for target amplitude, frequency and
99      * duration, that are forwarded to {@link VibrationEffect.WaveformBuilder#addTransition}.
100      *
101      * <p>This method returns {@code null} if the pattern is also {@code null} or invalid.
102      *
103      * @param values The list of values describing the waveform as a sequence of target amplitude,
104      *               frequency and duration.
105      * @param insistent {@code true} if the vibration should loop until it is cancelled.
106      */
107     @Nullable
createPwleWaveformVibration(@ullable float[] values, boolean insistent)108     public static VibrationEffect createPwleWaveformVibration(@Nullable float[] values,
109             boolean insistent) {
110         try {
111             if (values == null) {
112                 return null;
113             }
114 
115             int length = values.length;
116             // The waveform is described by triples (amplitude, frequency, duration)
117             if ((length == 0) || (length % 3 != 0)) {
118                 return null;
119             }
120 
121             VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform();
122             for (int i = 0; i < length; i += 3) {
123                 waveformBuilder.addTransition(Duration.ofMillis((int) values[i + 2]),
124                         targetAmplitude(values[i]), targetFrequency(values[i + 1]));
125             }
126 
127             VibrationEffect effect = waveformBuilder.build();
128             if (insistent) {
129                 return VibrationEffect.startComposition()
130                         .repeatEffectIndefinitely(effect)
131                         .compose();
132             }
133             return effect;
134         } catch (IllegalArgumentException e) {
135             Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: "
136                     + Arrays.toString(values));
137         }
138         return null;
139     }
140 
141     /**
142      *  Scale vibration effect, valid range is [0.0f, 1.0f]
143      *  Resolves default amplitude value if not already set.
144      */
scale(VibrationEffect effect, float scale)145     public VibrationEffect scale(VibrationEffect effect, float scale) {
146         return effect.resolve(mDefaultVibrationAmplitude).scale(scale);
147     }
148 
149     /**
150      * Vibrate the device with given {@code effect}.
151      *
152      * <p>We need to vibrate as "android" so we can breakthrough DND.
153      */
vibrate(VibrationEffect effect, AudioAttributes attrs, String reason)154     public void vibrate(VibrationEffect effect, AudioAttributes attrs, String reason) {
155         mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME,
156                 effect, reason, new VibrationAttributes.Builder(attrs).build());
157     }
158 
159     /** Stop all notification vibrations (ringtone, alarm, notification usages). */
cancelVibration()160     public void cancelVibration() {
161         int usageFilter =
162                 VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK;
163         mVibrator.cancel(usageFilter);
164     }
165 
166     /**
167      * Creates a vibration to be used as fallback when the device is in vibrate mode.
168      *
169      * @param insistent {@code true} if the vibration should loop until it is cancelled.
170      */
createFallbackVibration(boolean insistent)171     public VibrationEffect createFallbackVibration(boolean insistent) {
172         if (mVibrator.hasFrequencyControl()) {
173             VibrationEffect effect = createPwleWaveformVibration(mFallbackPwlePattern, insistent);
174             if (effect != null) {
175                 return effect;
176             }
177         }
178         return createWaveformVibration(mFallbackPattern, insistent);
179     }
180 
181     /**
182      * Creates a vibration to be used by notifications without a custom pattern.
183      *
184      * @param insistent {@code true} if the vibration should loop until it is cancelled.
185      */
createDefaultVibration(boolean insistent)186     public VibrationEffect createDefaultVibration(boolean insistent) {
187         if (mVibrator.hasFrequencyControl()) {
188             VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent);
189             if (effect != null) {
190                 return effect;
191             }
192         }
193         return createWaveformVibration(mDefaultPattern, insistent);
194     }
195 
196     /** Returns if a given vibration can be played by the vibrator that does notification buzz. */
areEffectComponentsSupported(VibrationEffect effect)197     public boolean areEffectComponentsSupported(VibrationEffect effect) {
198         return mVibrator.areVibrationFeaturesSupported(effect);
199     }
200 
201     @Nullable
getFloatArray(Resources resources, int resId)202     private static float[] getFloatArray(Resources resources, int resId) {
203         TypedArray array = resources.obtainTypedArray(resId);
204         try {
205             float[] values = new float[array.length()];
206             for (int i = 0; i < values.length; i++) {
207                 values[i] = array.getFloat(i, Float.NaN);
208                 if (Float.isNaN(values[i])) {
209                     return null;
210                 }
211             }
212             return values;
213         } finally {
214             array.recycle();
215         }
216     }
217 
getLongArray(Resources resources, int resId, int maxLength, long[] def)218     private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) {
219         int[] ar = resources.getIntArray(resId);
220         if (ar == null) {
221             return def;
222         }
223         final int len = ar.length > maxLength ? maxLength : ar.length;
224         long[] out = new long[len];
225         for (int i = 0; i < len; i++) {
226             out[i] = ar[i];
227         }
228         return out;
229     }
230 }
231