blob: 0aafaf456a0839b97b681fb0d5c17e48b6956852 [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.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Range;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Function;
/**
* 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();
@GuardedBy("mLock")
private VibratorInfo 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();
if (vibratorIds.length == 0) {
// It is known that the device has no vibrator, so cache and return info that
// reflects the lack of support for effects/primitives.
return mVibratorInfo = new NoVibratorInfo();
}
VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
for (int i = 0; i < vibratorIds.length; i++) {
Vibrator vibrator = mVibratorManager.getVibrator(vibratorIds[i]);
if (vibrator instanceof NullVibrator) {
Log.w(TAG, "Vibrator manager service not ready; "
+ "Info not yet available for vibrator: " + vibratorIds[i]);
// This should never happen after the vibrator manager service is ready.
// Skip caching this vibrator until then.
return VibratorInfo.EMPTY_VIBRATOR_INFO;
}
vibratorInfos[i] = vibrator.getInfo();
}
if (vibratorInfos.length == 1) {
// Device has a single vibrator info, cache and return successfully loaded info.
return mVibratorInfo = new VibratorInfo(/* id= */ -1, vibratorInfos[0]);
}
// Device has multiple vibrators, generate a single info representing all of them.
return mVibratorInfo = new MultiVibratorInfo(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,
VibrationAttributes attrs) {
if (mVibratorManager == null) {
Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
return false;
}
CombinedVibration combinedEffect = CombinedVibration.createParallel(effect);
return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attrs);
}
@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 a device with no vibrator as a single {@link VibratorInfo}.
*
* @hide
*/
@VisibleForTesting
public static class NoVibratorInfo extends VibratorInfo {
public NoVibratorInfo() {
// Use empty arrays to indicate no support, while null would indicate support unknown.
super(/* id= */ -1,
/* capabilities= */ 0,
/* supportedEffects= */ new SparseBooleanArray(),
/* supportedBraking= */ new SparseBooleanArray(),
/* supportedPrimitives= */ new SparseIntArray(),
/* primitiveDelayMax= */ 0,
/* compositionSizeMax= */ 0,
/* pwlePrimitiveDurationMax= */ 0,
/* pwleSizeMax= */ 0,
/* qFactor= */ Float.NaN,
new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN,
/* minFrequencyHz= */ Float.NaN,
/* frequencyResolutionHz= */ Float.NaN,
/* maxAmplitudes= */ null));
}
}
/**
* Represents multiple vibrator information as a single {@link VibratorInfo}.
*
* <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
* support.
*
* @hide
*/
@VisibleForTesting
public static class MultiVibratorInfo extends VibratorInfo {
// Epsilon used for float comparison applied in calculations for the merged info.
private static final float EPSILON = 1e-5f;
public MultiVibratorInfo(VibratorInfo[] vibrators) {
super(/* id= */ -1,
capabilitiesIntersection(vibrators),
supportedEffectsIntersection(vibrators),
supportedBrakingIntersection(vibrators),
supportedPrimitivesAndDurationsIntersection(vibrators),
integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax),
integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax),
integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
frequencyProfileIntersection(vibrators));
}
private static int capabilitiesIntersection(VibratorInfo[] infos) {
int intersection = ~0;
for (VibratorInfo info : infos) {
intersection &= info.getCapabilities();
}
return intersection;
}
@Nullable
private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
for (VibratorInfo info : infos) {
if (!info.isBrakingSupportKnown()) {
// If one vibrator support is unknown, then the intersection is also unknown.
return null;
}
}
SparseBooleanArray intersection = new SparseBooleanArray();
SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
brakingIdLoop:
for (int i = 0; i < firstVibratorBraking.size(); i++) {
int brakingId = firstVibratorBraking.keyAt(i);
if (!firstVibratorBraking.valueAt(i)) {
// The first vibrator already doesn't support this braking, so skip it.
continue brakingIdLoop;
}
for (int j = 1; j < infos.length; j++) {
if (!infos[j].hasBrakingSupport(brakingId)) {
// One vibrator doesn't support this braking, so the intersection doesn't.
continue brakingIdLoop;
}
}
intersection.put(brakingId, true);
}
return intersection;
}
@Nullable
private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
for (VibratorInfo info : infos) {
if (!info.isEffectSupportKnown()) {
// If one vibrator support is unknown, then the intersection is also unknown.
return null;
}
}
SparseBooleanArray intersection = new SparseBooleanArray();
SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
effectIdLoop:
for (int i = 0; i < firstVibratorEffects.size(); i++) {
int effectId = firstVibratorEffects.keyAt(i);
if (!firstVibratorEffects.valueAt(i)) {
// The first vibrator already doesn't support this effect, so skip it.
continue effectIdLoop;
}
for (int j = 1; j < infos.length; j++) {
if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) {
// One vibrator doesn't support this effect, so the intersection doesn't.
continue effectIdLoop;
}
}
intersection.put(effectId, true);
}
return intersection;
}
@NonNull
private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
VibratorInfo[] infos) {
SparseIntArray intersection = new SparseIntArray();
SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
primitiveIdLoop:
for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
int primitiveId = firstVibratorPrimitives.keyAt(i);
int primitiveDuration = firstVibratorPrimitives.valueAt(i);
if (primitiveDuration == 0) {
// The first vibrator already doesn't support this primitive, so skip it.
continue primitiveIdLoop;
}
for (int j = 1; j < infos.length; j++) {
int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
if (vibratorPrimitiveDuration == 0) {
// One vibrator doesn't support this primitive, so the intersection doesn't.
continue primitiveIdLoop;
} else {
// The primitive vibration duration is the maximum among all vibrators.
primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
}
}
intersection.put(primitiveId, primitiveDuration);
}
return intersection;
}
private static int integerLimitIntersection(VibratorInfo[] infos,
Function<VibratorInfo, Integer> propertyGetter) {
int limit = 0; // Limit 0 means unlimited
for (VibratorInfo info : infos) {
int vibratorLimit = propertyGetter.apply(info);
if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
// This vibrator is limited and intersection is unlimited or has a larger limit:
// use smaller limit here for the intersection.
limit = vibratorLimit;
}
}
return limit;
}
private static float floatPropertyIntersection(VibratorInfo[] infos,
Function<VibratorInfo, Float> propertyGetter) {
float property = propertyGetter.apply(infos[0]);
if (Float.isNaN(property)) {
// If one vibrator is undefined then the intersection is undefined.
return Float.NaN;
}
for (int i = 1; i < infos.length; i++) {
if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
// If one vibrator has a different value then the intersection is undefined.
return Float.NaN;
}
}
return property;
}
@NonNull
private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
float freqResolution = floatPropertyIntersection(infos,
info -> info.getFrequencyProfile().getFrequencyResolutionHz());
float resonantFreq = floatPropertyIntersection(infos,
VibratorInfo::getResonantFrequencyHz);
Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
if ((freqRange == null) || Float.isNaN(freqResolution)) {
return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
}
int amplitudeCount =
Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
float[] maxAmplitudes = new float[amplitudeCount];
// Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
// will fail if the loop below is broken and do not replace filled values with actual
// vibrator measurements.
Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
for (VibratorInfo info : infos) {
Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
int vibratorStartIdx = Math.round(
(freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
+ " profiles: attempted to fetch from vibrator "
+ info.getId() + " max amplitude with bad index " + vibratorStartIdx);
return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
}
for (int i = 0; i < maxAmplitudes.length; i++) {
maxAmplitudes[i] = Math.min(maxAmplitudes[i],
vibratorMaxAmplitudes[vibratorStartIdx + i]);
}
}
return new FrequencyProfile(resonantFreq, freqRange.getLower(),
freqResolution, maxAmplitudes);
}
@Nullable
private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
float frequencyResolution) {
Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
if (firstRange == null) {
// If one vibrator is undefined then the intersection is undefined.
return null;
}
float intersectionLower = firstRange.getLower();
float intersectionUpper = firstRange.getUpper();
// Generate the intersection of all vibrator supported ranges, making sure that both
// min supported frequencies are aligned w.r.t. the frequency resolution.
for (int i = 1; i < infos.length; i++) {
Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
if (vibratorRange == null) {
// If one vibrator is undefined then the intersection is undefined.
return null;
}
if ((vibratorRange.getLower() >= intersectionUpper)
|| (vibratorRange.getUpper() <= intersectionLower)) {
// If the range and intersection are disjoint then the intersection is undefined
return null;
}
float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
if ((frequencyDelta % frequencyResolution) > EPSILON) {
// If the intersection is not aligned with one vibrator then it's undefined
return null;
}
intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
}
if ((intersectionUpper - intersectionLower) < frequencyResolution) {
// If the intersection is empty then it's undefined.
return null;
}
return Range.create(intersectionLower, intersectionUpper);
}
}
/** 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);
});
}
}
}