blob: 0c3debb1b54eccfde2bce317a7d1a971bcdd75df [file] [log] [blame]
/*
* 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<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
@GuardedBy("mRegisteredListeners")
private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
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.
*
* <p>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.
*
* <p>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}.
*
* <p>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<SingleVibratorStateListener> 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);
});
}
}
}