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