summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/VibratorService.java57
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp30
-rw-r--r--services/tests/servicestests/src/com/android/server/VibratorServiceTest.java130
3 files changed, 164 insertions, 53 deletions
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 2fef82d352dd..80046e78858d 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -167,7 +167,7 @@ public class VibratorService extends IVibratorService.Stub
private boolean mIsVibrating;
@GuardedBy("mLock")
private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
- new RemoteCallbackList<>();
+ new RemoteCallbackList<>();
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
@@ -176,16 +176,25 @@ public class VibratorService extends IVibratorService.Stub
static native long vibratorInit();
static native long vibratorGetFinalizer();
+
static native boolean vibratorExists(long controllerPtr);
- static native void vibratorOn(long milliseconds);
+
+ static native void vibratorOn(long controllerPtr, long milliseconds, Vibration vibration);
+
static native void vibratorOff(long controllerPtr);
+
static native void vibratorSetAmplitude(long controllerPtr, int amplitude);
+
static native int[] vibratorGetSupportedEffects(long controllerPtr);
+
static native long vibratorPerformEffect(long effect, long strength, Vibration vibration,
boolean withCallback);
+
static native void vibratorPerformComposedEffect(long controllerPtr,
VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
+
static native void vibratorSetExternalControl(long controllerPtr, boolean enabled);
+
static native long vibratorGetCapabilities(long controllerPtr);
static native void vibratorAlwaysOnEnable(long controllerPtr, long id, long effect,
long strength);
@@ -231,8 +240,7 @@ public class VibratorService extends IVibratorService.Stub
// The actual effect to be played.
public VibrationEffect effect;
- // The original effect that was requested. This is non-null only when the original effect
- // differs from the effect that's being played. Typically these two things differ because
+ // The original effect that was requested. Typically these two things differ because
// the effect was scaled based on the users vibration intensity settings.
public VibrationEffect originalEffect;
@@ -248,6 +256,7 @@ public class VibratorService extends IVibratorService.Stub
this.reason = reason;
}
+ @Override
public void binderDied() {
synchronized (mLock) {
if (this == mCurrentVibration) {
@@ -949,25 +958,22 @@ public class VibratorService extends IVibratorService.Stub
private void startVibrationInnerLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
try {
+ long timeout = 0;
mCurrentVibration = vib;
if (vib.effect instanceof VibrationEffect.OneShot) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
- doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.attrs);
- mH.postDelayed(mVibrationEndRunnable, oneShot.getDuration());
+ doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib);
+ timeout = oneShot.getDuration() * ASYNC_TIMEOUT_MULTIPLIER;
} else if (vib.effect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
mThread = new VibrateThread(waveform, vib.uid, vib.attrs);
mThread.start();
} else if (vib.effect instanceof VibrationEffect.Prebaked) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- long timeout = doVibratorPrebakedEffectLocked(vib);
- if (timeout > 0) {
- mH.postDelayed(mVibrationEndRunnable, timeout);
- }
+ timeout = doVibratorPrebakedEffectLocked(vib);
} else if (vib.effect instanceof VibrationEffect.Composed) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
doVibratorComposedEffectLocked(vib);
@@ -975,10 +981,16 @@ public class VibratorService extends IVibratorService.Stub
// devices which support composition also support the completion callback. If we
// ever get a device that supports the former but not the latter, then we have no
// real way of knowing how long a given effect should last.
- mH.postDelayed(mVibrationEndRunnable, 10000);
+ timeout = 10_000;
} else {
Slog.e(TAG, "Unknown vibration type, ignoring");
}
+ // Post extra runnable to ensure vibration will end even if the HAL or native controller
+ // never triggers the callback.
+ // TODO: Move ASYNC_TIMEOUT_MULTIPLIER here once native controller is fully integrated.
+ if (timeout > 0) {
+ mH.postDelayed(mVibrationEndRunnable, timeout);
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
@@ -1271,7 +1283,18 @@ public class VibratorService extends IVibratorService.Stub
return mNativeWrapper.vibratorExists();
}
+ /** Vibrates with native callback trigger for {@link Vibration#onComplete()}. */
+ private void doVibratorOn(long millis, int amplitude, Vibration vib) {
+ doVibratorOn(millis, amplitude, vib.uid, vib.attrs, vib);
+ }
+
+ /** Vibrates without native callback. */
private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs) {
+ doVibratorOn(millis, amplitude, uid, attrs, /* vib= */ null);
+ }
+
+ private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs,
+ @Nullable Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
try {
synchronized (mInputDeviceVibrators) {
@@ -1286,13 +1309,14 @@ public class VibratorService extends IVibratorService.Stub
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
- mInputDeviceVibrators.get(i).vibrate(millis, attrs.getAudioAttributes());
+ Vibrator inputDeviceVibrator = mInputDeviceVibrators.get(i);
+ inputDeviceVibrator.vibrate(millis, attrs.getAudioAttributes());
}
} else {
// Note: ordering is important here! Many haptic drivers will reset their
// amplitude when enabled, so we always have to enable first, then set the
// amplitude.
- mNativeWrapper.vibratorOn(millis);
+ mNativeWrapper.vibratorOn(millis, vib);
doVibratorSetAmplitude(amplitude);
}
}
@@ -1576,6 +1600,7 @@ public class VibratorService extends IVibratorService.Stub
proto.flush();
}
+ /** Thread that plays a single {@link VibrationEffect.Waveform}. */
private class VibrateThread extends Thread {
private final VibrationEffect.Waveform mWaveform;
private final int mUid;
@@ -1744,8 +1769,8 @@ public class VibratorService extends IVibratorService.Stub
}
/** Turns vibrator on for given time. */
- public void vibratorOn(long milliseconds) {
- VibratorService.vibratorOn(milliseconds);
+ public void vibratorOn(long milliseconds, @Nullable Vibration vibration) {
+ VibratorService.vibratorOn(mNativeControllerPtr, milliseconds, vibration);
}
/** Turns vibrator off. */
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index f0618c23b025..b6633ced771b 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -231,6 +231,9 @@ bool isValidEffect(jlong effect) {
}
static void callVibrationOnComplete(jobject vibration) {
+ if (vibration == nullptr) {
+ return;
+ }
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
jniEnv->CallVoidMethod(vibration, sMethodIdOnComplete);
jniEnv->DeleteGlobalRef(vibration);
@@ -281,18 +284,16 @@ static jboolean vibratorExists(JNIEnv* env, jclass /* clazz */, jlong controller
return controller->ping().isOk() ? JNI_TRUE : JNI_FALSE;
}
-static void vibratorOn(JNIEnv* /* env */, jclass /* clazz */, jlong timeout_ms) {
- if (auto hal = getHal<aidl::IVibrator>()) {
- auto status = hal->call(&aidl::IVibrator::on, timeout_ms, nullptr);
- if (!status.isOk()) {
- ALOGE("vibratorOn command failed: %s", status.toString8().string());
- }
- } else {
- Status retStatus = halCall(&V1_0::IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);
- if (retStatus != Status::OK) {
- ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
- }
+static void vibratorOn(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong timeoutMs,
+ jobject vibration) {
+ vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+ if (controller == nullptr) {
+ ALOGE("vibratorOn failed because controller was not initialized");
+ return;
}
+ jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
+ auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
+ controller->on(std::chrono::milliseconds(timeoutMs), callback);
}
static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong controllerPtr) {
@@ -420,9 +421,8 @@ static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong
jobject element = env->GetObjectArrayElement(composition, i);
effects.push_back(effectFromJavaPrimitive(env, element));
}
- auto callback = [vibrationRef(MakeGlobalRefOrDie(env, vibration))]() {
- callVibrationOnComplete(vibrationRef);
- };
+ jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
+ auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
controller->performComposedEffect(effects, callback);
}
@@ -461,7 +461,7 @@ static const JNINativeMethod method_table[] = {
{"vibratorInit", "()J", (void*)vibratorInit},
{"vibratorGetFinalizer", "()J", (void*)vibratorGetFinalizer},
{"vibratorExists", "(J)Z", (void*)vibratorExists},
- {"vibratorOn", "(J)V", (void*)vibratorOn},
+ {"vibratorOn", "(JJLcom/android/server/VibratorService$Vibration;)V", (void*)vibratorOn},
{"vibratorOff", "(J)V", (void*)vibratorOff},
{"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
{"vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;Z)J",
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 1b6ac3c84210..7d6d90c4578c 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -26,7 +26,9 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.intThat;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -65,6 +67,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
@@ -277,7 +280,7 @@ public class VibratorServiceTest {
assertTrue(service.isVibrating());
verify(mNativeWrapperMock).vibratorOff();
- verify(mNativeWrapperMock).vibratorOn(eq(100L));
+ verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(128));
}
@@ -290,7 +293,7 @@ public class VibratorServiceTest {
assertTrue(service.isVibrating());
verify(mNativeWrapperMock).vibratorOff();
- verify(mNativeWrapperMock).vibratorOn(eq(100L));
+ verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
verify(mNativeWrapperMock, never()).vibratorSetAmplitude(anyInt());
}
@@ -344,76 +347,157 @@ public class VibratorServiceTest {
Mockito.clearInvocations(mNativeWrapperMock);
VibrationEffect effect = VibrationEffect.createWaveform(
- new long[] { 10, 10, 10 }, new int[] { 100, 200, 50 }, -1);
+ new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
vibrate(service, effect);
verify(mNativeWrapperMock).vibratorOff();
+ // Wait for VibrateThread to turn vibrator ON with total timing and no callback.
Thread.sleep(5);
- verify(mNativeWrapperMock).vibratorOn(eq(30L));
+ verify(mNativeWrapperMock).vibratorOn(eq(30L), isNull());
+
+ // First amplitude set right away.
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(100));
+ // Second amplitude set after first timing is finished.
Thread.sleep(10);
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(200));
+ // Third amplitude set after second timing is finished.
Thread.sleep(10);
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(50));
}
@Test
- public void vibrate_withCallback_finishesVibrationWhenCallbackTriggered() {
- mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ public void vibrate_withOneShotAndNativeCallbackTriggered_finishesVibration() {
+ doAnswer(invocation -> {
+ ((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
+ return null;
+ }).when(mNativeWrapperMock).vibratorOn(anyLong(), any(VibratorService.Vibration.class));
+ VibratorService service = createService();
+ Mockito.clearInvocations(mNativeWrapperMock);
+
+ vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+
+ InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(100L),
+ any(VibratorService.Vibration.class));
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ }
+
+ @Test
+ public void vibrate_withOneShotAndNativeCallbackNotTriggered_finishesVibrationViaFallback() {
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
+ vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+
+ verify(mNativeWrapperMock).vibratorOff();
+ verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
+ Mockito.clearInvocations(mNativeWrapperMock);
+
+ // Run the scheduled callback to finish one-shot vibration.
+ mTestLooper.moveTimeForward(200);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).vibratorOff();
+ }
+
+ @Test
+ public void vibrate_withWaveformAndNativeCallback_callbackCannotBeTriggeredByNative()
+ throws Exception {
+ VibratorService service = createService();
+ Mockito.clearInvocations(mNativeWrapperMock);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(new long[]{1, 3, 1, 2}, -1);
+ vibrate(service, effect);
+
+ // Wait for VibrateThread to finish: 1ms OFF, 3ms ON, 1ms OFF, 2ms ON.
+ Thread.sleep(15);
+ InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(3L), isNull());
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(2L), isNull());
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ }
+
+ @Test
+ public void vibrate_withComposedAndNativeCallbackTriggered_finishesVibration() {
+ mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
doAnswer(invocation -> {
((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
return null;
}).when(mNativeWrapperMock).vibratorPerformComposedEffect(
any(), any(VibratorService.Vibration.class));
+ VibratorService service = createService();
+ Mockito.clearInvocations(mNativeWrapperMock);
- // Use vibration with delay so there is time for the callback to be triggered.
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
.compose();
vibrate(service, effect);
- // Vibration canceled once before perform and once by native callback.
- verify(mNativeWrapperMock, times(2)).vibratorOff();
- verify(mNativeWrapperMock).vibratorPerformComposedEffect(
+ InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
any(VibrationEffect.Composition.PrimitiveEffect[].class),
any(VibratorService.Vibration.class));
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@Test
- public void vibrate_whenBinderDies_cancelsVibration() {
+ public void vibrate_withComposedAndNativeCallbackNotTriggered_finishesVibrationViaFallback() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
+ .compose();
+ vibrate(service, effect);
+
+ verify(mNativeWrapperMock).vibratorOff();
+ verify(mNativeWrapperMock).vibratorPerformComposedEffect(
+ any(VibrationEffect.Composition.PrimitiveEffect[].class),
+ any(VibratorService.Vibration.class));
+ Mockito.clearInvocations(mNativeWrapperMock);
+
+ // Run the scheduled callback to finish one-shot vibration.
+ mTestLooper.moveTimeForward(10000); // 10s
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).vibratorOff();
+ }
+
+ @Test
+ public void vibrate_whenBinderDies_cancelsVibration() {
+ mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
doAnswer(invocation -> {
((VibratorService.Vibration) invocation.getArgument(1)).binderDied();
return null;
}).when(mNativeWrapperMock).vibratorPerformComposedEffect(
any(), any(VibratorService.Vibration.class));
+ VibratorService service = createService();
+ Mockito.clearInvocations(mNativeWrapperMock);
- // Use vibration with delay so there is time for the callback to be triggered.
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
.compose();
vibrate(service, effect);
- // Vibration canceled once before perform and once by native binder death.
- verify(mNativeWrapperMock, times(2)).vibratorOff();
- verify(mNativeWrapperMock).vibratorPerformComposedEffect(
+ InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
any(VibrationEffect.Composition.PrimitiveEffect[].class),
any(VibratorService.Vibration.class));
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@Test
public void cancelVibrate_withDeviceVibrating_callsVibratorOff() {
VibratorService service = createService();
- vibrate(service, VibrationEffect.createOneShot(100, 128));
+ vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
assertTrue(service.isVibrating());
Mockito.clearInvocations(mNativeWrapperMock);
@@ -434,18 +518,20 @@ public class VibratorServiceTest {
@Test
public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+ doAnswer(invocation -> {
+ ((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
+ return null;
+ }).when(mNativeWrapperMock).vibratorOn(anyLong(), any(VibratorService.Vibration.class));
VibratorService service = createService();
service.registerVibratorStateListener(mVibratorStateListenerMock);
verify(mVibratorStateListenerMock).onVibrating(false);
+ Mockito.clearInvocations(mVibratorStateListenerMock);
vibrate(service, VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE));
- verify(mVibratorStateListenerMock).onVibrating(true);
-
- // Run the scheduled callback to finish one-shot vibration.
- mTestLooper.moveTimeForward(10);
- mTestLooper.dispatchAll();
- verify(mVibratorStateListenerMock, times(2)).onVibrating(false);
+ InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
+ inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
+ inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
}
@Test