1 /*
2  * Copyright (C) 2012 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 android.os;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.content.Context;
24 import android.hardware.vibrator.IVibrator;
25 import android.util.ArrayMap;
26 import android.util.Log;
27 import android.util.Range;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 import android.util.SparseBooleanArray;
31 import android.util.SparseIntArray;
32 
33 import com.android.internal.annotations.GuardedBy;
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Objects;
39 import java.util.concurrent.Executor;
40 import java.util.function.Function;
41 
42 /**
43  * Vibrator implementation that controls the main system vibrator.
44  *
45  * @hide
46  */
47 public class SystemVibrator extends Vibrator {
48     private static final String TAG = "Vibrator";
49 
50     private final VibratorManager mVibratorManager;
51     private final Context mContext;
52 
53     @GuardedBy("mBrokenListeners")
54     private final ArrayList<MultiVibratorStateListener> mBrokenListeners = new ArrayList<>();
55 
56     @GuardedBy("mRegisteredListeners")
57     private final ArrayMap<OnVibratorStateChangedListener, MultiVibratorStateListener>
58             mRegisteredListeners = new ArrayMap<>();
59 
60     private final Object mLock = new Object();
61     @GuardedBy("mLock")
62     private VibratorInfo mVibratorInfo;
63 
64     @UnsupportedAppUsage
SystemVibrator(Context context)65     public SystemVibrator(Context context) {
66         super(context);
67         mContext = context;
68         mVibratorManager = mContext.getSystemService(VibratorManager.class);
69     }
70 
71     @Override
getInfo()72     protected VibratorInfo getInfo() {
73         synchronized (mLock) {
74             if (mVibratorInfo != null) {
75                 return mVibratorInfo;
76             }
77             if (mVibratorManager == null) {
78                 Log.w(TAG, "Failed to retrieve vibrator info; no vibrator manager.");
79                 return VibratorInfo.EMPTY_VIBRATOR_INFO;
80             }
81             int[] vibratorIds = mVibratorManager.getVibratorIds();
82             if (vibratorIds.length == 0) {
83                 // It is known that the device has no vibrator, so cache and return info that
84                 // reflects the lack of support for effects/primitives.
85                 return mVibratorInfo = new NoVibratorInfo();
86             }
87             VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
88             for (int i = 0; i < vibratorIds.length; i++) {
89                 Vibrator vibrator = mVibratorManager.getVibrator(vibratorIds[i]);
90                 if (vibrator instanceof NullVibrator) {
91                     Log.w(TAG, "Vibrator manager service not ready; "
92                             + "Info not yet available for vibrator: " + vibratorIds[i]);
93                     // This should never happen after the vibrator manager service is ready.
94                     // Skip caching this vibrator until then.
95                     return VibratorInfo.EMPTY_VIBRATOR_INFO;
96                 }
97                 vibratorInfos[i] = vibrator.getInfo();
98             }
99             if (vibratorInfos.length == 1) {
100                 // Device has a single vibrator info, cache and return successfully loaded info.
101                 return mVibratorInfo = new VibratorInfo(/* id= */ -1, vibratorInfos[0]);
102             }
103             // Device has multiple vibrators, generate a single info representing all of them.
104             return mVibratorInfo = new MultiVibratorInfo(vibratorInfos);
105         }
106     }
107 
108     @Override
hasVibrator()109     public boolean hasVibrator() {
110         if (mVibratorManager == null) {
111             Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
112             return false;
113         }
114         return mVibratorManager.getVibratorIds().length > 0;
115     }
116 
117     @Override
isVibrating()118     public boolean isVibrating() {
119         if (mVibratorManager == null) {
120             Log.w(TAG, "Failed to vibrate; no vibrator manager.");
121             return false;
122         }
123         for (int vibratorId : mVibratorManager.getVibratorIds()) {
124             if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
125                 return true;
126             }
127         }
128         return false;
129     }
130 
131     @Override
addVibratorStateListener(@onNull OnVibratorStateChangedListener listener)132     public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
133         Objects.requireNonNull(listener);
134         if (mContext == null) {
135             Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
136             return;
137         }
138         addVibratorStateListener(mContext.getMainExecutor(), listener);
139     }
140 
141     @Override
addVibratorStateListener( @onNull @allbackExecutor Executor executor, @NonNull OnVibratorStateChangedListener listener)142     public void addVibratorStateListener(
143             @NonNull @CallbackExecutor Executor executor,
144             @NonNull OnVibratorStateChangedListener listener) {
145         Objects.requireNonNull(listener);
146         Objects.requireNonNull(executor);
147         if (mVibratorManager == null) {
148             Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
149             return;
150         }
151         MultiVibratorStateListener delegate = null;
152         try {
153             synchronized (mRegisteredListeners) {
154                 // If listener is already registered, reject and return.
155                 if (mRegisteredListeners.containsKey(listener)) {
156                     Log.w(TAG, "Listener already registered.");
157                     return;
158                 }
159                 delegate = new MultiVibratorStateListener(executor, listener);
160                 delegate.register(mVibratorManager);
161                 mRegisteredListeners.put(listener, delegate);
162                 delegate = null;
163             }
164         } finally {
165             if (delegate != null && delegate.hasRegisteredListeners()) {
166                 // The delegate listener was left in a partial state with listeners registered to
167                 // some but not all vibrators. Keep track of this to try to unregister them later.
168                 synchronized (mBrokenListeners) {
169                     mBrokenListeners.add(delegate);
170                 }
171             }
172             tryUnregisterBrokenListeners();
173         }
174     }
175 
176     @Override
removeVibratorStateListener(@onNull OnVibratorStateChangedListener listener)177     public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
178         Objects.requireNonNull(listener);
179         if (mVibratorManager == null) {
180             Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
181             return;
182         }
183         synchronized (mRegisteredListeners) {
184             if (mRegisteredListeners.containsKey(listener)) {
185                 MultiVibratorStateListener delegate = mRegisteredListeners.get(listener);
186                 delegate.unregister(mVibratorManager);
187                 mRegisteredListeners.remove(listener);
188             }
189         }
190         tryUnregisterBrokenListeners();
191     }
192 
193     @Override
hasAmplitudeControl()194     public boolean hasAmplitudeControl() {
195         return getInfo().hasAmplitudeControl();
196     }
197 
198     @Override
setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, VibrationAttributes attrs)199     public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
200             VibrationAttributes attrs) {
201         if (mVibratorManager == null) {
202             Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
203             return false;
204         }
205         CombinedVibration combinedEffect = CombinedVibration.createParallel(effect);
206         return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attrs);
207     }
208 
209     @Override
vibrate(int uid, String opPkg, @NonNull VibrationEffect effect, String reason, @NonNull VibrationAttributes attributes)210     public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
211             String reason, @NonNull VibrationAttributes attributes) {
212         if (mVibratorManager == null) {
213             Log.w(TAG, "Failed to vibrate; no vibrator manager.");
214             return;
215         }
216         CombinedVibration combinedEffect = CombinedVibration.createParallel(effect);
217         mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
218     }
219 
220     @Override
cancel()221     public void cancel() {
222         if (mVibratorManager == null) {
223             Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
224             return;
225         }
226         mVibratorManager.cancel();
227     }
228 
229     @Override
cancel(int usageFilter)230     public void cancel(int usageFilter) {
231         if (mVibratorManager == null) {
232             Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
233             return;
234         }
235         mVibratorManager.cancel(usageFilter);
236     }
237 
238     /**
239      * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
240      * that were left registered to vibrators after failures to register them to all vibrators.
241      *
242      * <p>This might happen if {@link MultiVibratorStateListener} fails to register to any vibrator
243      * and also fails to unregister any previously registered single listeners to other vibrators.
244      *
245      * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
246      * fail silently and attempt to unregister the same broken listener later.
247      */
tryUnregisterBrokenListeners()248     private void tryUnregisterBrokenListeners() {
249         synchronized (mBrokenListeners) {
250             try {
251                 for (int i = mBrokenListeners.size(); --i >= 0; ) {
252                     mBrokenListeners.get(i).unregister(mVibratorManager);
253                     mBrokenListeners.remove(i);
254                 }
255             } catch (RuntimeException e) {
256                 Log.w(TAG, "Failed to unregister broken listener", e);
257             }
258         }
259     }
260 
261     /** Listener for a single vibrator state change. */
262     private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
263         private final MultiVibratorStateListener mAllVibratorsListener;
264         private final int mVibratorIdx;
265 
SingleVibratorStateListener(MultiVibratorStateListener listener, int vibratorIdx)266         SingleVibratorStateListener(MultiVibratorStateListener listener, int vibratorIdx) {
267             mAllVibratorsListener = listener;
268             mVibratorIdx = vibratorIdx;
269         }
270 
271         @Override
onVibratorStateChanged(boolean isVibrating)272         public void onVibratorStateChanged(boolean isVibrating) {
273             mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating);
274         }
275     }
276 
277     /**
278      * Represents a device with no vibrator as a single {@link VibratorInfo}.
279      *
280      * @hide
281      */
282     @VisibleForTesting
283     public static class NoVibratorInfo extends VibratorInfo {
NoVibratorInfo()284         public NoVibratorInfo() {
285             // Use empty arrays to indicate no support, while null would indicate support unknown.
286             super(/* id= */ -1,
287                     /* capabilities= */ 0,
288                     /* supportedEffects= */ new SparseBooleanArray(),
289                     /* supportedBraking= */ new SparseBooleanArray(),
290                     /* supportedPrimitives= */ new SparseIntArray(),
291                     /* primitiveDelayMax= */ 0,
292                     /* compositionSizeMax= */ 0,
293                     /* pwlePrimitiveDurationMax= */ 0,
294                     /* pwleSizeMax= */ 0,
295                     /* qFactor= */ Float.NaN,
296                     new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN,
297                             /* minFrequencyHz= */ Float.NaN,
298                             /* frequencyResolutionHz= */ Float.NaN,
299                             /* maxAmplitudes= */ null));
300         }
301     }
302 
303     /**
304      * Represents multiple vibrator information as a single {@link VibratorInfo}.
305      *
306      * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
307      * support.
308      *
309      * @hide
310      */
311     @VisibleForTesting
312     public static class MultiVibratorInfo extends VibratorInfo {
313         // Epsilon used for float comparison applied in calculations for the merged info.
314         private static final float EPSILON = 1e-5f;
315 
MultiVibratorInfo(VibratorInfo[] vibrators)316         public MultiVibratorInfo(VibratorInfo[] vibrators) {
317             // Need to use an extra constructor to share the computation in super initialization.
318             this(vibrators, frequencyProfileIntersection(vibrators));
319         }
320 
MultiVibratorInfo(VibratorInfo[] vibrators, VibratorInfo.FrequencyProfile mergedProfile)321         private MultiVibratorInfo(VibratorInfo[] vibrators,
322                 VibratorInfo.FrequencyProfile mergedProfile) {
323             super(/* id= */ -1,
324                     capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
325                     supportedEffectsIntersection(vibrators),
326                     supportedBrakingIntersection(vibrators),
327                     supportedPrimitivesAndDurationsIntersection(vibrators),
328                     integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax),
329                     integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax),
330                     integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
331                     integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
332                     floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
333                     mergedProfile);
334         }
335 
capabilitiesIntersection(VibratorInfo[] infos, boolean frequencyProfileIsEmpty)336         private static int capabilitiesIntersection(VibratorInfo[] infos,
337                 boolean frequencyProfileIsEmpty) {
338             int intersection = ~0;
339             for (VibratorInfo info : infos) {
340                 intersection &= info.getCapabilities();
341             }
342             if (frequencyProfileIsEmpty) {
343                 // Revoke frequency control if the merged frequency profile ended up empty.
344                 intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
345             }
346             return intersection;
347         }
348 
349         @Nullable
supportedBrakingIntersection(VibratorInfo[] infos)350         private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
351             for (VibratorInfo info : infos) {
352                 if (!info.isBrakingSupportKnown()) {
353                     // If one vibrator support is unknown, then the intersection is also unknown.
354                     return null;
355                 }
356             }
357 
358             SparseBooleanArray intersection = new SparseBooleanArray();
359             SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
360 
361             brakingIdLoop:
362             for (int i = 0; i < firstVibratorBraking.size(); i++) {
363                 int brakingId = firstVibratorBraking.keyAt(i);
364                 if (!firstVibratorBraking.valueAt(i)) {
365                     // The first vibrator already doesn't support this braking, so skip it.
366                     continue brakingIdLoop;
367                 }
368 
369                 for (int j = 1; j < infos.length; j++) {
370                     if (!infos[j].hasBrakingSupport(brakingId)) {
371                         // One vibrator doesn't support this braking, so the intersection doesn't.
372                         continue brakingIdLoop;
373                     }
374                 }
375 
376                 intersection.put(brakingId, true);
377             }
378 
379             return intersection;
380         }
381 
382         @Nullable
supportedEffectsIntersection(VibratorInfo[] infos)383         private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
384             for (VibratorInfo info : infos) {
385                 if (!info.isEffectSupportKnown()) {
386                     // If one vibrator support is unknown, then the intersection is also unknown.
387                     return null;
388                 }
389             }
390 
391             SparseBooleanArray intersection = new SparseBooleanArray();
392             SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
393 
394             effectIdLoop:
395             for (int i = 0; i < firstVibratorEffects.size(); i++) {
396                 int effectId = firstVibratorEffects.keyAt(i);
397                 if (!firstVibratorEffects.valueAt(i)) {
398                     // The first vibrator already doesn't support this effect, so skip it.
399                     continue effectIdLoop;
400                 }
401 
402                 for (int j = 1; j < infos.length; j++) {
403                     if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) {
404                         // One vibrator doesn't support this effect, so the intersection doesn't.
405                         continue effectIdLoop;
406                     }
407                 }
408 
409                 intersection.put(effectId, true);
410             }
411 
412             return intersection;
413         }
414 
415         @NonNull
supportedPrimitivesAndDurationsIntersection( VibratorInfo[] infos)416         private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
417                 VibratorInfo[] infos) {
418             SparseIntArray intersection = new SparseIntArray();
419             SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
420 
421             primitiveIdLoop:
422             for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
423                 int primitiveId = firstVibratorPrimitives.keyAt(i);
424                 int primitiveDuration = firstVibratorPrimitives.valueAt(i);
425                 if (primitiveDuration == 0) {
426                     // The first vibrator already doesn't support this primitive, so skip it.
427                     continue primitiveIdLoop;
428                 }
429 
430                 for (int j = 1; j < infos.length; j++) {
431                     int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
432                     if (vibratorPrimitiveDuration == 0) {
433                         // One vibrator doesn't support this primitive, so the intersection doesn't.
434                         continue primitiveIdLoop;
435                     } else {
436                         // The primitive vibration duration is the maximum among all vibrators.
437                         primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
438                     }
439                 }
440 
441                 intersection.put(primitiveId, primitiveDuration);
442             }
443             return intersection;
444         }
445 
integerLimitIntersection(VibratorInfo[] infos, Function<VibratorInfo, Integer> propertyGetter)446         private static int integerLimitIntersection(VibratorInfo[] infos,
447                 Function<VibratorInfo, Integer> propertyGetter) {
448             int limit = 0; // Limit 0 means unlimited
449             for (VibratorInfo info : infos) {
450                 int vibratorLimit = propertyGetter.apply(info);
451                 if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
452                     // This vibrator is limited and intersection is unlimited or has a larger limit:
453                     // use smaller limit here for the intersection.
454                     limit = vibratorLimit;
455                 }
456             }
457             return limit;
458         }
459 
floatPropertyIntersection(VibratorInfo[] infos, Function<VibratorInfo, Float> propertyGetter)460         private static float floatPropertyIntersection(VibratorInfo[] infos,
461                 Function<VibratorInfo, Float> propertyGetter) {
462             float property = propertyGetter.apply(infos[0]);
463             if (Float.isNaN(property)) {
464                 // If one vibrator is undefined then the intersection is undefined.
465                 return Float.NaN;
466             }
467             for (int i = 1; i < infos.length; i++) {
468                 if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
469                     // If one vibrator has a different value then the intersection is undefined.
470                     return Float.NaN;
471                 }
472             }
473             return property;
474         }
475 
476         @NonNull
frequencyProfileIntersection(VibratorInfo[] infos)477         private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
478             float freqResolution = floatPropertyIntersection(infos,
479                     info -> info.getFrequencyProfile().getFrequencyResolutionHz());
480             float resonantFreq = floatPropertyIntersection(infos,
481                     VibratorInfo::getResonantFrequencyHz);
482             Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
483 
484             if ((freqRange == null) || Float.isNaN(freqResolution)) {
485                 return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
486             }
487 
488             int amplitudeCount =
489                     Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
490             float[] maxAmplitudes = new float[amplitudeCount];
491 
492             // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
493             // will fail if the loop below is broken and do not replace filled values with actual
494             // vibrator measurements.
495             Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
496 
497             for (VibratorInfo info : infos) {
498                 Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
499                 float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
500                 int vibratorStartIdx = Math.round(
501                         (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
502                 int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
503 
504                 if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
505                     Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
506                             + " profiles: attempted to fetch from vibrator "
507                             + info.getId() + " max amplitude with bad index " + vibratorStartIdx);
508                     return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
509                 }
510 
511                 for (int i = 0; i < maxAmplitudes.length; i++) {
512                     maxAmplitudes[i] = Math.min(maxAmplitudes[i],
513                             vibratorMaxAmplitudes[vibratorStartIdx + i]);
514                 }
515             }
516 
517             return new FrequencyProfile(resonantFreq, freqRange.getLower(),
518                     freqResolution, maxAmplitudes);
519         }
520 
521         @Nullable
frequencyRangeIntersection(VibratorInfo[] infos, float frequencyResolution)522         private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
523                 float frequencyResolution) {
524             Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
525             if (firstRange == null) {
526                 // If one vibrator is undefined then the intersection is undefined.
527                 return null;
528             }
529             float intersectionLower = firstRange.getLower();
530             float intersectionUpper = firstRange.getUpper();
531 
532             // Generate the intersection of all vibrator supported ranges, making sure that both
533             // min supported frequencies are aligned w.r.t. the frequency resolution.
534 
535             for (int i = 1; i < infos.length; i++) {
536                 Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
537                 if (vibratorRange == null) {
538                     // If one vibrator is undefined then the intersection is undefined.
539                     return null;
540                 }
541 
542                 if ((vibratorRange.getLower() >= intersectionUpper)
543                         || (vibratorRange.getUpper() <= intersectionLower)) {
544                     // If the range and intersection are disjoint then the intersection is undefined
545                     return null;
546                 }
547 
548                 float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
549                 if ((frequencyDelta % frequencyResolution) > EPSILON) {
550                     // If the intersection is not aligned with one vibrator then it's undefined
551                     return null;
552                 }
553 
554                 intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
555                 intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
556             }
557 
558             if ((intersectionUpper - intersectionLower) < frequencyResolution) {
559                 // If the intersection is empty then it's undefined.
560                 return null;
561             }
562 
563             return Range.create(intersectionLower, intersectionUpper);
564         }
565     }
566 
567     /**
568      * Listener for all vibrators state change.
569      *
570      * <p>This registers a listener to all vibrators to merge the callbacks into a single state
571      * that is set to true if any individual vibrator is also true, and false otherwise.
572      *
573      * @hide
574      */
575     @VisibleForTesting
576     public static class MultiVibratorStateListener {
577         private final Object mLock = new Object();
578         private final Executor mExecutor;
579         private final OnVibratorStateChangedListener mDelegate;
580 
581         @GuardedBy("mLock")
582         private final SparseArray<SingleVibratorStateListener> mVibratorListeners =
583                 new SparseArray<>();
584 
585         @GuardedBy("mLock")
586         private int mInitializedMask;
587         @GuardedBy("mLock")
588         private int mVibratingMask;
589 
MultiVibratorStateListener(@onNull Executor executor, @NonNull OnVibratorStateChangedListener listener)590         public MultiVibratorStateListener(@NonNull Executor executor,
591                 @NonNull OnVibratorStateChangedListener listener) {
592             mExecutor = executor;
593             mDelegate = listener;
594         }
595 
596         /** Returns true if at least one listener was registered to an individual vibrator. */
hasRegisteredListeners()597         public boolean hasRegisteredListeners() {
598             synchronized (mLock) {
599                 return mVibratorListeners.size() > 0;
600             }
601         }
602 
603         /** Registers a listener to all individual vibrators in {@link VibratorManager}. */
register(VibratorManager vibratorManager)604         public void register(VibratorManager vibratorManager) {
605             int[] vibratorIds = vibratorManager.getVibratorIds();
606             synchronized (mLock) {
607                 for (int i = 0; i < vibratorIds.length; i++) {
608                     int vibratorId = vibratorIds[i];
609                     SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i);
610                     try {
611                         vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor,
612                                 listener);
613                         mVibratorListeners.put(vibratorId, listener);
614                     } catch (RuntimeException e) {
615                         try {
616                             unregister(vibratorManager);
617                         } catch (RuntimeException e1) {
618                             Log.w(TAG,
619                                     "Failed to unregister listener while recovering from a failed "
620                                             + "register call", e1);
621                         }
622                         throw e;
623                     }
624                 }
625             }
626         }
627 
628         /** Unregisters the listeners from all individual vibrators in {@link VibratorManager}. */
unregister(VibratorManager vibratorManager)629         public void unregister(VibratorManager vibratorManager) {
630             synchronized (mLock) {
631                 for (int i = mVibratorListeners.size(); --i >= 0; ) {
632                     int vibratorId = mVibratorListeners.keyAt(i);
633                     SingleVibratorStateListener listener = mVibratorListeners.valueAt(i);
634                     vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener);
635                     mVibratorListeners.removeAt(i);
636                 }
637             }
638         }
639 
640         /** Callback triggered by {@link SingleVibratorStateListener} for each vibrator. */
onVibrating(int vibratorIdx, boolean vibrating)641         public void onVibrating(int vibratorIdx, boolean vibrating) {
642             mExecutor.execute(() -> {
643                 boolean shouldNotifyStateChange;
644                 boolean isAnyVibrating;
645                 synchronized (mLock) {
646                     // Bitmask indicating that all vibrators have been initialized.
647                     int allInitializedMask = (1 << mVibratorListeners.size()) - 1;
648 
649                     // Save current global state before processing this vibrator state change.
650                     boolean previousIsAnyVibrating = (mVibratingMask != 0);
651                     boolean previousAreAllInitialized = (mInitializedMask == allInitializedMask);
652 
653                     // Mark this vibrator as initialized.
654                     int vibratorMask = (1 << vibratorIdx);
655                     mInitializedMask |= vibratorMask;
656 
657                     // Flip the vibrating bit flag for this vibrator, only if the state is changing.
658                     boolean previousVibrating = (mVibratingMask & vibratorMask) != 0;
659                     if (previousVibrating != vibrating) {
660                         mVibratingMask ^= vibratorMask;
661                     }
662 
663                     // Check new global state after processing this vibrator state change.
664                     isAnyVibrating = (mVibratingMask != 0);
665                     boolean areAllInitialized = (mInitializedMask == allInitializedMask);
666 
667                     // Prevent multiple triggers with the same state.
668                     // Trigger once when all vibrators have reported their state, and then only when
669                     // the merged vibrating state changes.
670                     boolean isStateChanging = (previousIsAnyVibrating != isAnyVibrating);
671                     shouldNotifyStateChange =
672                             areAllInitialized && (!previousAreAllInitialized || isStateChanging);
673                 }
674                 // Notify delegate listener outside the lock, only if merged state is changing.
675                 if (shouldNotifyStateChange) {
676                     mDelegate.onVibratorStateChanged(isAnyVibrating);
677                 }
678             });
679         }
680     }
681 }
682