Merge "Create parcelable VibratorInfo to expose HAL profile"

This commit is contained in:
TreeHugger Robot
2020-12-10 15:13:38 +00:00
committed by Android (Google) Code Review
14 changed files with 521 additions and 206 deletions

View File

@@ -18,10 +18,12 @@ package android.os;
import android.os.CombinedVibrationEffect;
import android.os.VibrationAttributes;
import android.os.VibratorInfo;
/** {@hide} */
interface IVibratorManagerService {
int[] getVibratorIds();
VibratorInfo getVibratorInfo(int vibratorId);
boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
in CombinedVibrationEffect effect, in VibrationAttributes attributes);
void vibrate(int uid, String opPkg, in CombinedVibrationEffect effect,

View File

@@ -18,6 +18,7 @@ package android.os;
import android.os.VibrationEffect;
import android.os.VibrationAttributes;
import android.os.VibratorInfo;
import android.os.IVibratorStateListener;
/** {@hide} */
@@ -25,11 +26,10 @@ interface IVibratorService
{
boolean hasVibrator();
boolean isVibrating();
VibratorInfo getVibratorInfo();
boolean registerVibratorStateListener(in IVibratorStateListener listener);
boolean unregisterVibratorStateListener(in IVibratorStateListener listener);
boolean hasAmplitudeControl();
int[] areEffectsSupported(in int[] effectIds);
boolean[] arePrimitivesSupported(in int[] primitiveIds);
void vibrate(int uid, String opPkg, in VibrationEffect effect,
in VibrationAttributes attributes, String reason, IBinder token);
void cancelVibrate(IBinder token);

View File

@@ -18,6 +18,7 @@ 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.media.AudioAttributes;
@@ -39,8 +40,11 @@ public class SystemVibrator extends Vibrator {
private final IVibratorService mService;
private final IVibratorManagerService mManagerService;
private final Object mLock = new Object();
private final Binder mToken = new Binder();
private final Context mContext;
@GuardedBy("mLock")
private VibratorInfo mVibratorInfo;
@GuardedBy("mDelegates")
private final ArrayMap<OnVibratorStateChangedListener,
@@ -242,23 +246,26 @@ public class SystemVibrator extends Vibrator {
@Override
public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
try {
return mService.areEffectsSupported(effectIds);
} catch (RemoteException e) {
Log.w(TAG, "Failed to query effect support");
throw e.rethrowAsRuntimeException();
VibratorInfo vibratorInfo = getVibratorInfo();
int[] supported = new int[effectIds.length];
for (int i = 0; i < effectIds.length; i++) {
supported[i] = vibratorInfo == null
? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN
: vibratorInfo.isEffectSupported(effectIds[i]);
}
return supported;
}
@Override
public boolean[] arePrimitivesSupported(
@NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
try {
return mService.arePrimitivesSupported(primitiveIds);
} catch (RemoteException e) {
Log.w(TAG, "Failed to query effect support");
throw e.rethrowAsRuntimeException();
VibratorInfo vibratorInfo = getVibratorInfo();
boolean[] supported = new boolean[primitiveIds.length];
for (int i = 0; i < primitiveIds.length; i++) {
supported[i] = vibratorInfo == null
? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]);
}
return supported;
}
@Override
@@ -272,4 +279,22 @@ public class SystemVibrator extends Vibrator {
Log.w(TAG, "Failed to cancel vibration.", e);
}
}
@Nullable
private VibratorInfo getVibratorInfo() {
try {
synchronized (mLock) {
if (mVibratorInfo != null) {
return mVibratorInfo;
}
if (mService == null) {
return null;
}
return mVibratorInfo = mService.getVibratorInfo();
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to query vibrator info");
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -476,6 +476,28 @@ public abstract class VibrationEffect implements Parcelable {
return a * fx;
}
/** @hide */
public static String effectIdToString(int effectId) {
switch (effectId) {
case EFFECT_CLICK:
return "CLICK";
case EFFECT_TICK:
return "TICK";
case EFFECT_HEAVY_CLICK:
return "HEAVY_CLICK";
case EFFECT_DOUBLE_CLICK:
return "DOUBLE_CLICK";
case EFFECT_POP:
return "POP";
case EFFECT_THUD:
return "THUD";
case EFFECT_TEXTURE_TICK:
return "TEXTURE_TICK";
default:
return Integer.toString(effectId);
}
}
/** @hide */
@TestApi
public static class OneShot extends VibrationEffect implements Parcelable {
@@ -1201,10 +1223,8 @@ public abstract class VibrationEffect implements Parcelable {
return "PRIMITIVE_QUICK_FALL";
case PRIMITIVE_TICK:
return "PRIMITIVE_TICK";
default:
return Integer.toString(id);
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 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;
parcelable VibratorInfo;

View File

@@ -0,0 +1,229 @@
/*
* Copyright (C) 2020 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.NonNull;
import android.annotation.Nullable;
import android.util.SparseBooleanArray;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* A VibratorInfo describes the capabilities of a {@link Vibrator}.
*
* This description includes its capabilities, list of supported effects and composition primitives.
*
* @hide
*/
public final class VibratorInfo implements Parcelable {
/**
* Capability to set amplitude values to vibrations.
* @hide
*/
// Internally this maps to the HAL constant IVibrator::CAP_AMPLITUDE_CONTROL
public static final int CAPABILITY_AMPLITUDE_CONTROL = 4;
/**
* Capability to compose primitives into a single effect.
* @hide
*/
// Internally this maps to the HAL constant IVibrator::CAP_COMPOSE_EFFECTS
public static final int CAPABILITY_COMPOSE_EFFECTS = 32;
private final int mId;
private final long mCapabilities;
@Nullable
private final SparseBooleanArray mSupportedEffects;
@Nullable
private final SparseBooleanArray mSupportedPrimitives;
VibratorInfo(Parcel in) {
mId = in.readInt();
mCapabilities = in.readLong();
mSupportedEffects = in.readSparseBooleanArray();
mSupportedPrimitives = in.readSparseBooleanArray();
}
/** @hide */
public VibratorInfo(int id, long capabilities, int[] supportedEffects,
int[] supportedPrimitives) {
mId = id;
mCapabilities = capabilities;
mSupportedEffects = toSparseBooleanArray(supportedEffects);
mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mId);
dest.writeLong(mCapabilities);
dest.writeSparseBooleanArray(mSupportedEffects);
dest.writeSparseBooleanArray(mSupportedPrimitives);
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof VibratorInfo)) {
return false;
}
VibratorInfo that = (VibratorInfo) o;
return mId == that.mId && mCapabilities == that.mCapabilities
&& Objects.equals(mSupportedEffects, that.mSupportedEffects)
&& Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives);
}
@Override
public int hashCode() {
return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives);
}
@Override
public String toString() {
return "VibratorInfo{"
+ "mId=" + mId
+ ", mCapabilities=" + Arrays.toString(getCapabilitiesNames())
+ ", mCapabilities flags=" + mCapabilities
+ ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
+ ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
+ '}';
}
/** Return the id of this vibrator. */
public int getId() {
return mId;
}
/**
* Check whether the vibrator has amplitude control.
*
* @return True if the hardware can control the amplitude of the vibrations, otherwise false.
*/
public boolean hasAmplitudeControl() {
return hasCapability(CAPABILITY_AMPLITUDE_CONTROL);
}
/**
* Query whether the vibrator supports the given effect.
*
* @param effectId Which effects to query for.
* @return {@link Vibrator#VIBRATION_EFFECT_SUPPORT_YES} if the effect is supported,
* {@link Vibrator#VIBRATION_EFFECT_SUPPORT_NO} if it isn't supported, or
* {@link Vibrator#VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether it's
* supported or not.
*/
@Vibrator.VibrationEffectSupport
public int isEffectSupported(@VibrationEffect.EffectType int effectId) {
if (mSupportedEffects == null) {
return Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
}
return mSupportedEffects.get(effectId, false) ? Vibrator.VIBRATION_EFFECT_SUPPORT_YES
: Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
}
/**
* Query whether the vibrator supports the given primitive.
*
* @param primitiveId Which primitives to query for.
* @return Whether the primitive is supported.
*/
public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) {
return hasCapability(CAPABILITY_COMPOSE_EFFECTS) && mSupportedPrimitives != null
&& mSupportedPrimitives.get(primitiveId, false);
}
/**
* Check against this vibrator capabilities.
*
* @param capability one of IVibrator.CAP_*
* @return true if this vibrator has this capability, false otherwise
* @hide
*/
public boolean hasCapability(long capability) {
return (mCapabilities & capability) == capability;
}
private String[] getCapabilitiesNames() {
List<String> names = new ArrayList<>();
if (hasCapability(CAPABILITY_AMPLITUDE_CONTROL)) {
names.add("AMPLITUDE_CONTROL");
}
if (hasCapability(CAPABILITY_COMPOSE_EFFECTS)) {
names.add("COMPOSE_EFFECTS");
}
return names.toArray(new String[names.size()]);
}
private String[] getSupportedEffectsNames() {
if (mSupportedEffects == null) {
return new String[0];
}
String[] names = new String[mSupportedEffects.size()];
for (int i = 0; i < mSupportedEffects.size(); i++) {
names[i] = VibrationEffect.effectIdToString(mSupportedEffects.keyAt(i));
}
return names;
}
private String[] getSupportedPrimitivesNames() {
if (mSupportedPrimitives == null) {
return new String[0];
}
String[] names = new String[mSupportedPrimitives.size()];
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i));
}
return names;
}
@Nullable
private static SparseBooleanArray toSparseBooleanArray(int[] values) {
if (values == null) {
return null;
}
SparseBooleanArray array = new SparseBooleanArray();
for (int value : values) {
array.put(value, true);
}
return array;
}
@NonNull
public static final Creator<VibratorInfo> CREATOR =
new Creator<VibratorInfo>() {
@Override
public VibratorInfo createFromParcel(Parcel in) {
return new VibratorInfo(in);
}
@Override
public VibratorInfo[] newArray(int size) {
return new VibratorInfo[size];
}
};
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2020 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 static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@Presubmit
@RunWith(JUnit4.class)
public class VibratorInfoTest {
@Test
public void testHasAmplitudeControl() {
assertFalse(createInfo(/* capabilities= */ 0).hasAmplitudeControl());
assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS
| VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL).hasAmplitudeControl());
}
@Test
public void testHasCapabilities() {
assertTrue(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS)
.hasCapability(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS));
assertFalse(createInfo(VibratorInfo.CAPABILITY_COMPOSE_EFFECTS)
.hasCapability(VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL));
}
@Test
public void testIsEffectSupported() {
VibratorInfo info = new VibratorInfo(/* id= */ 0, /* capabilities= */0,
new int[]{VibrationEffect.EFFECT_CLICK}, null);
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
createInfo(/* capabilities= */ 0).isEffectSupported(VibrationEffect.EFFECT_CLICK));
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
info.isEffectSupported(VibrationEffect.EFFECT_TICK));
}
@Test
public void testIsPrimitiveSupported() {
VibratorInfo info = new VibratorInfo(/* id= */ 0, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
// Returns false when there is no compose capability.
info = new VibratorInfo(/* id= */ 0, /* capabilities= */ 0,
null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@Test
public void testEquals() {
VibratorInfo empty = new VibratorInfo(1, 0, null, null);
VibratorInfo complete = new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
new int[]{VibrationEffect.EFFECT_CLICK},
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
assertEquals(complete, complete);
assertEquals(complete, new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
new int[]{VibrationEffect.EFFECT_CLICK},
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}));
assertFalse(empty.equals(new VibratorInfo(1, 0, new int[]{}, new int[]{})));
assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
new int[]{VibrationEffect.EFFECT_CLICK},
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
new int[]{}, new int[]{})));
assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
assertFalse(complete.equals(new VibratorInfo(1, VibratorInfo.CAPABILITY_AMPLITUDE_CONTROL,
new int[]{VibrationEffect.EFFECT_CLICK}, null)));
}
@Test
public void testSerialization() {
VibratorInfo original = new VibratorInfo(1, VibratorInfo.CAPABILITY_COMPOSE_EFFECTS,
new int[]{VibrationEffect.EFFECT_CLICK}, null);
Parcel parcel = Parcel.obtain();
original.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
VibratorInfo restored = VibratorInfo.CREATOR.createFromParcel(parcel);
assertEquals(original, restored);
}
private static VibratorInfo createInfo(long capabilities) {
return new VibratorInfo(/* id= */ 0, capabilities, null, null);
}
}

View File

@@ -54,6 +54,26 @@ public class VibratorTest {
mVibratorSpy = spy(InstrumentationRegistry.getContext().getSystemService(Vibrator.class));
}
@Test
public void areEffectsSupported_returnsArrayOfSameSize() {
assertEquals(0, mVibratorSpy.areEffectsSupported(new int[0]).length);
assertEquals(1,
mVibratorSpy.areEffectsSupported(new int[]{VibrationEffect.EFFECT_CLICK}).length);
assertEquals(2,
mVibratorSpy.areEffectsSupported(new int[]{VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_TICK}).length);
}
@Test
public void arePrimitivesSupported_returnsArrayOfSameSize() {
assertEquals(0, mVibratorSpy.arePrimitivesSupported(new int[0]).length);
assertEquals(1, mVibratorSpy.arePrimitivesSupported(
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}).length);
assertEquals(2, mVibratorSpy.arePrimitivesSupported(
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE}).length);
}
@Test
public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);

View File

@@ -37,6 +37,7 @@ import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.util.Slog;
import android.util.SparseArray;
@@ -165,6 +166,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
@Override // Binder call
@Nullable
public VibratorInfo getVibratorInfo(int vibratorId) {
VibratorController controller = mVibrators.get(vibratorId);
return controller == null ? null : controller.getVibratorInfo();
}
@Override // Binder call
public int[] getVibratorIds() {
return Arrays.copyOf(mVibratorIds, mVibratorIds.length);

View File

@@ -51,6 +51,7 @@ import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.WorkSource;
import android.util.Slog;
import android.util.SparseArray;
@@ -343,6 +344,11 @@ public class VibratorService extends IVibratorService.Stub {
return mVibratorController.isVibrating();
}
@Override // Binder call
public VibratorInfo getVibratorInfo() {
return mVibratorController.getVibratorInfo();
}
@Override // Binder call
public boolean registerVibratorStateListener(IVibratorStateListener listener) {
if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
@@ -367,16 +373,6 @@ public class VibratorService extends IVibratorService.Stub {
|| mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
}
@Override // Binder call
public int[] areEffectsSupported(int[] effectIds) {
return mVibratorController.areEffectsSupported(effectIds);
}
@Override // Binder call
public boolean[] arePrimitivesSupported(int[] primitiveIds) {
return mVibratorController.arePrimitivesSupported(primitiveIds);
}
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;

View File

@@ -23,7 +23,7 @@ import android.os.IVibratorStateListener;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -31,10 +31,6 @@ import com.android.internal.annotations.VisibleForTesting;
import libcore.util.NativeAllocationRegistry;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/** Controls a single vibrator. */
// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
public final class VibratorController {
@@ -42,12 +38,7 @@ public final class VibratorController {
private final Object mLock = new Object();
private final NativeWrapper mNativeWrapper;
private final int mVibratorId;
private final long mCapabilities;
@Nullable
private final Set<Integer> mSupportedEffects;
@Nullable
private final Set<Integer> mSupportedPrimitives;
private final VibratorInfo mVibratorInfo;
@GuardedBy("mLock")
private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
@@ -115,13 +106,11 @@ public final class VibratorController {
@VisibleForTesting
public VibratorController(int vibratorId, OnVibrationCompleteListener listener,
NativeWrapper nativeWrapper) {
mVibratorId = vibratorId;
mNativeWrapper = nativeWrapper;
mNativeWrapper.init(vibratorId, listener);
nativeWrapper.init(vibratorId, listener);
mCapabilities = nativeWrapper.getCapabilities();
mSupportedEffects = asSet(nativeWrapper.getSupportedEffects());
mSupportedPrimitives = asSet(nativeWrapper.getSupportedPrimitives());
mVibratorInfo = new VibratorInfo(vibratorId, nativeWrapper.getCapabilities(),
nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives());
}
/** Register state listener for this vibrator. */
@@ -153,9 +142,9 @@ public final class VibratorController {
}
}
/** Return the id of the vibrator controlled by this instance. */
public int getVibratorId() {
return mVibratorId;
/** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
public VibratorInfo getVibratorInfo() {
return mVibratorInfo;
}
/**
@@ -184,43 +173,7 @@ public final class VibratorController {
* @return true if this vibrator has this capability, false otherwise
*/
public boolean hasCapability(long capability) {
return (mCapabilities & capability) == capability;
}
/**
* Check against this vibrator supported effects.
*
* @param effectIds list of effects, one of VibrationEffect.EFFECT_*
* @return one entry per requested effectId, with one of Vibrator.VIBRATION_EFFECT_SUPPORT_*
*/
public int[] areEffectsSupported(int[] effectIds) {
int[] supported = new int[effectIds.length];
if (mSupportedEffects == null) {
Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN);
} else {
for (int i = 0; i < effectIds.length; i++) {
supported[i] = mSupportedEffects.contains(effectIds[i])
? Vibrator.VIBRATION_EFFECT_SUPPORT_YES
: Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
}
}
return supported;
}
/**
* Check against this vibrator supported primitives.
*
* @param primitiveIds list of primitives, one of VibrationEffect.Composition.EFFECT_*
* @return one entry per requested primitiveId, with true if it is supported
*/
public boolean[] arePrimitivesSupported(int[] primitiveIds) {
boolean[] supported = new boolean[primitiveIds.length];
if (mSupportedPrimitives != null && hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
for (int i = 0; i < primitiveIds.length; i++) {
supported[i] = mSupportedPrimitives.contains(primitiveIds[i]);
}
}
return supported;
return mVibratorInfo.hasCapability(capability);
}
/** Return {@code true} if the underlying vibrator is currently available, false otherwise. */
@@ -234,7 +187,7 @@ public final class VibratorController {
* <p>This will affect the state of {@link #isUnderExternalControl()}.
*/
public void setExternalControl(boolean externalControl) {
if (!hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
return;
}
synchronized (mLock) {
@@ -248,7 +201,7 @@ public final class VibratorController {
* if given {@code effect} is {@code null}.
*/
public void updateAlwaysOn(int id, @Nullable VibrationEffect.Prebaked effect) {
if (!hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
return;
}
synchronized (mLock) {
@@ -263,7 +216,7 @@ public final class VibratorController {
/** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */
public void setAmplitude(int amplitude) {
synchronized (mLock) {
if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
mNativeWrapper.setAmplitude(amplitude);
}
}
@@ -306,7 +259,7 @@ public final class VibratorController {
* <p>This will affect the state of {@link #isVibrating()}.
*/
public void on(VibrationEffect.Composed effect, long vibrationId) {
if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
return;
}
synchronized (mLock) {
@@ -327,10 +280,7 @@ public final class VibratorController {
@Override
public String toString() {
return "VibratorController{"
+ "mVibratorId=" + mVibratorId
+ ", mCapabilities=" + mCapabilities
+ ", mSupportedEffects=" + mSupportedEffects
+ ", mSupportedPrimitives=" + mSupportedPrimitives
+ "mVibratorInfo=" + mVibratorInfo
+ ", mIsVibrating=" + mIsVibrating
+ ", mIsUnderExternalControl=" + mIsUnderExternalControl
+ ", mVibratorStateListeners count="
@@ -375,18 +325,6 @@ public final class VibratorController {
}
}
@Nullable
private static Set<Integer> asSet(int[] values) {
if (values == null) {
return null;
}
HashSet<Integer> set = new HashSet<>();
for (int value : values) {
set.add(value);
}
return set;
}
/** Wrapper around the static-native methods of {@link VibratorController} for tests. */
@VisibleForTesting
public static class NativeWrapper {

View File

@@ -19,7 +19,10 @@ package com.android.server;
import static com.android.server.testutils.TestUtils.assertExpectException;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -38,6 +41,8 @@ import android.os.PowerSaveState;
import android.os.Process;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -134,17 +139,48 @@ public class VibratorManagerServiceTest {
@Test
public void getVibratorIds_withNonEmptyResultFromNative_returnsSameArray() {
mNativeWrappers.put(1, mockVibrator(0));
mNativeWrappers.put(2, mockVibrator(0));
mNativeWrappers.put(1, mockVibrator(/* capabilities= */ 0));
mNativeWrappers.put(2, mockVibrator(/* capabilities= */ 0));
when(mNativeWrapperMock.getVibratorIds()).thenReturn(new int[]{2, 1});
assertArrayEquals(new int[]{2, 1}, createService().getVibratorIds());
}
@Test
public void getVibratorInfo_withMissingVibratorId_returnsNull() {
mockVibrators(mockVibrator(/* capabilities= */ 0));
assertNull(createService().getVibratorInfo(2));
}
@Test
public void getVibratorInfo_withExistingVibratorId_returnsHalInfoForVibrator() {
VibratorController.NativeWrapper vibratorMock = mockVibrator(
IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL);
when(vibratorMock.getSupportedEffects()).thenReturn(
new int[]{VibrationEffect.EFFECT_CLICK});
when(vibratorMock.getSupportedPrimitives()).thenReturn(
new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
mNativeWrappers.put(1, vibratorMock);
when(mNativeWrapperMock.getVibratorIds()).thenReturn(new int[]{1});
VibratorInfo info = createService().getVibratorInfo(1);
assertNotNull(info);
assertEquals(1, info.getId());
assertTrue(info.hasAmplitudeControl());
assertTrue(info.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
assertFalse(info.hasCapability(IVibrator.CAP_ON_CALLBACK));
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
info.isEffectSupported(VibrationEffect.EFFECT_TICK));
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
}
@Test
public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
VibratorController.NativeWrapper[] vibratorMocks = new VibratorController.NativeWrapper[] {
VibratorController.NativeWrapper[] vibratorMocks = new VibratorController.NativeWrapper[]{
mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL),
mockVibrator(0),
mockVibrator(/* capabilities= */ 0),
mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL),
};
mockVibrators(vibratorMocks);

View File

@@ -16,7 +16,6 @@
package com.android.server;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -62,6 +61,7 @@ import android.os.UserHandle;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
@@ -235,56 +235,21 @@ public class VibratorServiceTest {
}
@Test
public void areEffectsSupported_withNullResultFromNative_returnsSupportUnknown() {
when(mNativeWrapperMock.getSupportedEffects()).thenReturn(null);
assertArrayEquals(new int[]{Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN},
createService().areEffectsSupported(new int[]{VibrationEffect.EFFECT_CLICK}));
}
@Test
public void areEffectsSupported_withSomeEffectsSupported_returnsSupportYesAndNoForEffects() {
int[] effects = new int[]{VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK};
public void getVibratorInfo_returnsSameInfoFromNative() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL);
when(mNativeWrapperMock.getSupportedEffects())
.thenReturn(new int[]{VibrationEffect.EFFECT_CLICK});
assertArrayEquals(
new int[]{Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
Vibrator.VIBRATION_EFFECT_SUPPORT_NO},
createService().areEffectsSupported(effects));
}
@Test
public void arePrimitivesSupported_withoutComposeCapability_returnsAlwaysFalse() {
assertArrayEquals(new boolean[]{false, false},
createService().arePrimitivesSupported(new int[]{
VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_TICK
}));
}
@Test
public void arePrimitivesSupported_withNullResultFromNative_returnsAlwaysFalse() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
when(mNativeWrapperMock.getSupportedPrimitives()).thenReturn(null);
assertArrayEquals(new boolean[]{false, false},
createService().arePrimitivesSupported(new int[]{
VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
}));
}
@Test
public void arePrimitivesSupported_withSomeSupportedPrimitives_returnsBasedOnNativeResult() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
when(mNativeWrapperMock.getSupportedPrimitives())
.thenReturn(new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
assertArrayEquals(new boolean[]{true, false},
createService().arePrimitivesSupported(new int[]{
VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
}));
VibratorInfo info = createService().getVibratorInfo();
assertTrue(info.hasAmplitudeControl());
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
info.isEffectSupported(VibrationEffect.EFFECT_TICK));
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
}
@Test

View File

@@ -16,7 +16,6 @@
package com.android.server.vibrator;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -38,7 +37,6 @@ import android.hardware.vibrator.IVibrator;
import android.os.IBinder;
import android.os.IVibratorStateListener;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -100,7 +98,7 @@ public class VibratorControllerTest {
public void createController_initializesNativeWrapper() {
int vibratorId = 13;
VibratorController controller = createController(vibratorId);
assertEquals(vibratorId, controller.getVibratorId());
assertEquals(vibratorId, controller.getVibratorInfo().getId());
verify(mNativeWrapperMock).init(eq(vibratorId), notNull());
}
@@ -131,59 +129,6 @@ public class VibratorControllerTest {
assertFalse(createController().hasCapability(IVibrator.CAP_ON_CALLBACK));
}
@Test
public void areEffectsSupported_withNullResultFromNative_returnsSupportUnknown() {
when(mNativeWrapperMock.getSupportedEffects()).thenReturn(null);
assertArrayEquals(new int[]{Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN},
createController().areEffectsSupported(new int[]{VibrationEffect.EFFECT_CLICK}));
}
@Test
public void areEffectsSupported_withSomeEffectsSupported_returnsSupportYesAndNoForEffects() {
int[] effects = new int[]{VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK};
when(mNativeWrapperMock.getSupportedEffects())
.thenReturn(new int[]{VibrationEffect.EFFECT_CLICK});
assertArrayEquals(
new int[]{Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
Vibrator.VIBRATION_EFFECT_SUPPORT_NO},
createController().areEffectsSupported(effects));
}
@Test
public void arePrimitivesSupported_withoutComposeCapability_returnsAlwaysFalse() {
assertArrayEquals(new boolean[]{false, false},
createController().arePrimitivesSupported(new int[]{
VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_TICK
}));
}
@Test
public void arePrimitivesSupported_withNullResultFromNative_returnsAlwaysFalse() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
when(mNativeWrapperMock.getSupportedPrimitives()).thenReturn(null);
assertArrayEquals(new boolean[]{false, false},
createController().arePrimitivesSupported(new int[]{
VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
}));
}
@Test
public void arePrimitivesSupported_withSomeSupportedPrimitives_returnsBasedOnNativeResult() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
when(mNativeWrapperMock.getSupportedPrimitives())
.thenReturn(new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
assertArrayEquals(new boolean[]{true, false},
createController().arePrimitivesSupported(new int[]{
VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
}));
}
@Test
public void setExternalControl_withCapability_enablesExternalControl() {
mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);