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