/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.media.AudioAttributes; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; /** * Vibrator implementation that controls the main system vibrator. * * @hide */ public class SystemVibrator extends Vibrator { private static final String TAG = "Vibrator"; private final VibratorManager mVibratorManager; private final Context mContext; @GuardedBy("mBrokenListeners") private final ArrayList mBrokenListeners = new ArrayList<>(); @GuardedBy("mRegisteredListeners") private final ArrayMap mRegisteredListeners = new ArrayMap<>(); private final Object mLock = new Object(); private AllVibratorsInfo mVibratorInfo; @UnsupportedAppUsage public SystemVibrator(Context context) { super(context); mContext = context; mVibratorManager = mContext.getSystemService(VibratorManager.class); } @Override protected VibratorInfo getInfo() { synchronized (mLock) { if (mVibratorInfo != null) { return mVibratorInfo; } if (mVibratorManager == null) { Log.w(TAG, "Failed to retrieve vibrator info; no vibrator manager."); return VibratorInfo.EMPTY_VIBRATOR_INFO; } int[] vibratorIds = mVibratorManager.getVibratorIds(); VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length]; for (int i = 0; i < vibratorIds.length; i++) { vibratorInfos[i] = mVibratorManager.getVibrator(vibratorIds[i]).getInfo(); } return mVibratorInfo = new AllVibratorsInfo(vibratorInfos); } } @Override public boolean hasVibrator() { if (mVibratorManager == null) { Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager."); return false; } return mVibratorManager.getVibratorIds().length > 0; } @Override public boolean isVibrating() { if (mVibratorManager == null) { Log.w(TAG, "Failed to vibrate; no vibrator manager."); return false; } for (int vibratorId : mVibratorManager.getVibratorIds()) { if (mVibratorManager.getVibrator(vibratorId).isVibrating()) { return true; } } return false; } @Override public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); if (mContext == null) { Log.w(TAG, "Failed to add vibrate state listener; no vibrator context."); return; } addVibratorStateListener(mContext.getMainExecutor(), listener); } @Override public void addVibratorStateListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); Objects.requireNonNull(executor); if (mVibratorManager == null) { Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager."); return; } AllVibratorsStateListener delegate = null; try { synchronized (mRegisteredListeners) { // If listener is already registered, reject and return. if (mRegisteredListeners.containsKey(listener)) { Log.w(TAG, "Listener already registered."); return; } delegate = new AllVibratorsStateListener(executor, listener); delegate.register(mVibratorManager); mRegisteredListeners.put(listener, delegate); delegate = null; } } finally { if (delegate != null && delegate.hasRegisteredListeners()) { // The delegate listener was left in a partial state with listeners registered to // some but not all vibrators. Keep track of this to try to unregister them later. synchronized (mBrokenListeners) { mBrokenListeners.add(delegate); } } tryUnregisterBrokenListeners(); } } @Override public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); if (mVibratorManager == null) { Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager."); return; } synchronized (mRegisteredListeners) { if (mRegisteredListeners.containsKey(listener)) { AllVibratorsStateListener delegate = mRegisteredListeners.get(listener); delegate.unregister(mVibratorManager); mRegisteredListeners.remove(listener); } } tryUnregisterBrokenListeners(); } @Override public boolean hasAmplitudeControl() { return getInfo().hasAmplitudeControl(); } @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, AudioAttributes attributes) { if (mVibratorManager == null) { Log.w(TAG, "Failed to set always-on effect; no vibrator manager."); return false; } VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build(); CombinedVibration combinedEffect = CombinedVibration.createParallel(effect); return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr); } @Override public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect, String reason, @NonNull VibrationAttributes attributes) { if (mVibratorManager == null) { Log.w(TAG, "Failed to vibrate; no vibrator manager."); return; } CombinedVibration combinedEffect = CombinedVibration.createParallel(effect); mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes); } @Override public void cancel() { if (mVibratorManager == null) { Log.w(TAG, "Failed to cancel vibrate; no vibrator manager."); return; } mVibratorManager.cancel(); } @Override public void cancel(int usageFilter) { if (mVibratorManager == null) { Log.w(TAG, "Failed to cancel vibrate; no vibrator manager."); return; } mVibratorManager.cancel(usageFilter); } /** * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener} * that were left registered to vibrators after failures to register them to all vibrators. * *

This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator * and also fails to unregister any previously registered single listeners to other vibrators. * *

This method never throws {@link RuntimeException} if it fails to unregister again, it will * fail silently and attempt to unregister the same broken listener later. */ private void tryUnregisterBrokenListeners() { synchronized (mBrokenListeners) { try { for (int i = mBrokenListeners.size(); --i >= 0; ) { mBrokenListeners.get(i).unregister(mVibratorManager); mBrokenListeners.remove(i); } } catch (RuntimeException e) { Log.w(TAG, "Failed to unregister broken listener", e); } } } /** Listener for a single vibrator state change. */ private static class SingleVibratorStateListener implements OnVibratorStateChangedListener { private final AllVibratorsStateListener mAllVibratorsListener; private final int mVibratorIdx; SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) { mAllVibratorsListener = listener; mVibratorIdx = vibratorIdx; } @Override public void onVibratorStateChanged(boolean isVibrating) { mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating); } } /** * Represents all the vibrators information as a single {@link VibratorInfo}. * *

This uses the first vibrator on the list as the default one for all hardware spec, but * uses an intersection of all vibrators to decide the capabilities and effect/primitive * support. * * @hide */ @VisibleForTesting public static class AllVibratorsInfo extends VibratorInfo { private final VibratorInfo[] mVibratorInfos; public AllVibratorsInfo(VibratorInfo[] vibrators) { super(/* id= */ -1, capabilitiesIntersection(vibrators), vibrators.length > 0 ? vibrators[0] : VibratorInfo.EMPTY_VIBRATOR_INFO); mVibratorInfos = vibrators; } @Override public int isEffectSupported(int effectId) { if (mVibratorInfos.length == 0) { return Vibrator.VIBRATION_EFFECT_SUPPORT_NO; } int supported = Vibrator.VIBRATION_EFFECT_SUPPORT_YES; for (VibratorInfo info : mVibratorInfos) { int effectSupported = info.isEffectSupported(effectId); if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) { return effectSupported; } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) { supported = effectSupported; } } return supported; } @Override public boolean isPrimitiveSupported(int primitiveId) { if (mVibratorInfos.length == 0) { return false; } for (VibratorInfo info : mVibratorInfos) { if (!info.isPrimitiveSupported(primitiveId)) { return false; } } return true; } @Override public int getPrimitiveDuration(int primitiveId) { int maxDuration = 0; for (VibratorInfo info : mVibratorInfos) { int duration = info.getPrimitiveDuration(primitiveId); if (duration == 0) { return 0; } maxDuration = Math.max(maxDuration, duration); } return maxDuration; } private static int capabilitiesIntersection(VibratorInfo[] infos) { if (infos.length == 0) { return 0; } int intersection = ~0; for (VibratorInfo info : infos) { intersection &= info.getCapabilities(); } return intersection; } } /** Listener for all vibrators state change. */ private static class AllVibratorsStateListener { private final Object mLock = new Object(); private final Executor mExecutor; private final OnVibratorStateChangedListener mDelegate; @GuardedBy("mLock") private final SparseArray mVibratorListeners = new SparseArray<>(); @GuardedBy("mLock") private int mInitializedMask; @GuardedBy("mLock") private int mVibratingMask; AllVibratorsStateListener(@NonNull Executor executor, @NonNull OnVibratorStateChangedListener listener) { mExecutor = executor; mDelegate = listener; } boolean hasRegisteredListeners() { synchronized (mLock) { return mVibratorListeners.size() > 0; } } void register(VibratorManager vibratorManager) { int[] vibratorIds = vibratorManager.getVibratorIds(); synchronized (mLock) { for (int i = 0; i < vibratorIds.length; i++) { int vibratorId = vibratorIds[i]; SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i); try { vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor, listener); mVibratorListeners.put(vibratorId, listener); } catch (RuntimeException e) { try { unregister(vibratorManager); } catch (RuntimeException e1) { Log.w(TAG, "Failed to unregister listener while recovering from a failed " + "register call", e1); } throw e; } } } } void unregister(VibratorManager vibratorManager) { synchronized (mLock) { for (int i = mVibratorListeners.size(); --i >= 0; ) { int vibratorId = mVibratorListeners.keyAt(i); SingleVibratorStateListener listener = mVibratorListeners.valueAt(i); vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener); mVibratorListeners.removeAt(i); } } } void onVibrating(int vibratorIdx, boolean vibrating) { mExecutor.execute(() -> { boolean anyVibrating; synchronized (mLock) { int allInitializedMask = 1 << mVibratorListeners.size() - 1; int vibratorMask = 1 << vibratorIdx; if ((mInitializedMask & vibratorMask) == 0) { // First state report for this vibrator, set vibrating initial value. mInitializedMask |= vibratorMask; mVibratingMask |= vibrating ? vibratorMask : 0; } else { // Flip vibrating value, if changed. boolean prevVibrating = (mVibratingMask & vibratorMask) != 0; if (prevVibrating != vibrating) { mVibratingMask ^= vibratorMask; } } if (mInitializedMask != allInitializedMask) { // Wait for all vibrators initial state to be reported before delegating. return; } anyVibrating = mVibratingMask != 0; } mDelegate.onVibratorStateChanged(anyVibrating); }); } } }