Create parcelable VibratorInfo to expose HAL profile

This parcelable will replace the separated methods hasAmplitudeControl,
areEffectsSupported and arePrimitiveSupported from VibratorService.

VibratorManagerService will return this single parcelable, per vibator
id, to represent the HAL profile of that individual vibrator and be used
by Vibrator.java to explose some of that to the public API.

Bug: 167946816
Test: atest VibratorInfoTest
      atest VibratorManagerServiceTest
Change-Id: I09c191dd15a3cf38122ec6dc87b2d73f46c8cde6
This commit is contained in:
Lais Andrade
2020-11-17 15:47:50 +00:00
parent 9e755fb3fe
commit 61ddb9473f
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);