Use vibrator manager controller in VibratorManagerService
Add native methods to load manager capabilities and coordinate synced
vibrations in VibrationThread.
Bug: 167946816
Bug: 167946760
Test: VibratorManagerTest, VibrationThreadTest
Change-Id: I54dad9420be1e8cddae9023f8438581d3f553412
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 37d2cdc..96cfe02 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -103,7 +103,7 @@
"android.hardware.gnss-V1-java",
"android.hardware.power-V1-java",
"android.hardware.power-V1.0-java",
- "android.hardware.vibrator-V1-java",
+ "android.hardware.vibrator-V2-java",
"android.net.ipsec.ike.stubs.module_lib",
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index 6a816af..e7e5d67 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -111,6 +111,7 @@
private final AppOpsManager mAppOps;
private final NativeWrapper mNativeWrapper;
private final VibratorManagerRecords mVibratorManagerRecords;
+ private final long mCapabilities;
private final int[] mVibratorIds;
private final SparseArray<VibratorController> mVibrators;
private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
@@ -127,18 +128,28 @@
private VibrationScaler mVibrationScaler;
private InputDeviceDelegate mInputDeviceDelegate;
- static native long nativeInit();
+ static native long nativeInit(OnSyncedVibrationCompleteListener listener);
static native long nativeGetFinalizer();
+ static native long nativeGetCapabilities(long nativeServicePtr);
+
static native int[] nativeGetVibratorIds(long nativeServicePtr);
+ static native boolean nativePrepareSynced(long nativeServicePtr, int[] vibratorIds);
+
+ static native boolean nativeTriggerSynced(long nativeServicePtr, long vibrationId);
+
+ static native void nativeCancelSynced(long nativeServicePtr);
+
@VisibleForTesting
VibratorManagerService(Context context, Injector injector) {
mContext = context;
mHandler = injector.createHandler(Looper.myLooper());
+
+ VibrationCompleteListener listener = new VibrationCompleteListener(this);
mNativeWrapper = injector.getNativeWrapper();
- mNativeWrapper.init();
+ mNativeWrapper.init(listener);
int dumpLimit = mContext.getResources().getInteger(
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
@@ -153,6 +164,7 @@
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
+ mCapabilities = mNativeWrapper.getCapabilities();
int[] vibratorIds = mNativeWrapper.getVibratorIds();
if (vibratorIds == null) {
mVibratorIds = new int[0];
@@ -161,11 +173,17 @@
// Keep original vibrator id order, which might be meaningful.
mVibratorIds = vibratorIds;
mVibrators = new SparseArray<>(mVibratorIds.length);
- VibrationCompleteListener listener = new VibrationCompleteListener(this);
for (int vibratorId : vibratorIds) {
mVibrators.put(vibratorId, injector.createVibratorController(vibratorId, listener));
}
}
+
+ // Reset the hardware to a default state, in case this is a runtime restart instead of a
+ // fresh boot.
+ mNativeWrapper.cancelSynced();
+ for (int i = 0; i < mVibrators.size(); i++) {
+ mVibrators.valueAt(i).off();
+ }
}
/** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
@@ -502,6 +520,17 @@
}
}
+ private void onSyncedVibrationComplete(long vibrationId) {
+ synchronized (mLock) {
+ if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
+ }
+ mCurrentVibration.syncedVibrationComplete();
+ }
+ }
+ }
+
private void onVibrationComplete(int vibratorId, long vibrationId) {
synchronized (mLock) {
if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
@@ -839,13 +868,22 @@
private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
@Override
- public void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds) {
- // TODO(b/167946816): call IVibratorManager to prepare
+ public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
+ if ((mCapabilities & requiredCapabilities) != requiredCapabilities) {
+ // This sync step requires capabilities this device doesn't have, skipping sync...
+ return false;
+ }
+ return mNativeWrapper.prepareSynced(vibratorIds);
}
@Override
- public void triggerSyncedVibration(long vibrationId) {
- // TODO(b/167946816): call IVibratorManager to trigger
+ public boolean triggerSyncedVibration(long vibrationId) {
+ return mNativeWrapper.triggerSynced(vibrationId);
+ }
+
+ @Override
+ public void cancelSyncedVibration() {
+ mNativeWrapper.cancelSynced();
}
@Override
@@ -868,12 +906,19 @@
}
}
+ /** Listener for synced vibration completion callbacks from native. */
+ @VisibleForTesting
+ public interface OnSyncedVibrationCompleteListener {
+
+ /** Callback triggered when synced vibration is complete. */
+ void onComplete(long vibrationId);
+ }
+
/**
- * Implementation of {@link VibratorController.OnVibrationCompleteListener} with a weak
- * reference to this service.
+ * Implementation of listeners to native vibrators with a weak reference to this service.
*/
private static final class VibrationCompleteListener implements
- VibratorController.OnVibrationCompleteListener {
+ VibratorController.OnVibrationCompleteListener, OnSyncedVibrationCompleteListener {
private WeakReference<VibratorManagerService> mServiceRef;
VibrationCompleteListener(VibratorManagerService service) {
@@ -881,6 +926,14 @@
}
@Override
+ public void onComplete(long vibrationId) {
+ VibratorManagerService service = mServiceRef.get();
+ if (service != null) {
+ service.onSyncedVibrationComplete(vibrationId);
+ }
+ }
+
+ @Override
public void onComplete(int vibratorId, long vibrationId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
@@ -952,9 +1005,9 @@
private long mNativeServicePtr = 0;
/** Returns native pointer to newly created controller and connects with HAL service. */
- public void init() {
- mNativeServicePtr = VibratorManagerService.nativeInit();
- long finalizerPtr = VibratorManagerService.nativeGetFinalizer();
+ public void init(OnSyncedVibrationCompleteListener listener) {
+ mNativeServicePtr = nativeInit(listener);
+ long finalizerPtr = nativeGetFinalizer();
if (finalizerPtr != 0) {
NativeAllocationRegistry registry =
@@ -964,9 +1017,29 @@
}
}
+ /** Returns manager capabilities. */
+ public long getCapabilities() {
+ return nativeGetCapabilities(mNativeServicePtr);
+ }
+
/** Returns vibrator ids. */
public int[] getVibratorIds() {
- return VibratorManagerService.nativeGetVibratorIds(mNativeServicePtr);
+ return nativeGetVibratorIds(mNativeServicePtr);
+ }
+
+ /** Prepare vibrators for triggering vibrations in sync. */
+ public boolean prepareSynced(@NonNull int[] vibratorIds) {
+ return nativePrepareSynced(mNativeServicePtr, vibratorIds);
+ }
+
+ /** Trigger prepared synced vibration. */
+ public boolean triggerSynced(long vibrationId) {
+ return nativeTriggerSynced(mNativeServicePtr, vibrationId);
+ }
+
+ /** Cancel prepared synced vibration. */
+ public void cancelSynced() {
+ nativeCancelSynced(mNativeServicePtr);
}
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 026eb63..2ac365d 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -119,11 +119,17 @@
private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
@Override
- public void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds) {
+ public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
+ return false;
}
@Override
- public void triggerSyncedVibration(long vibrationId) {
+ public boolean triggerSyncedVibration(long vibrationId) {
+ return false;
+ }
+
+ @Override
+ public void cancelSyncedVibration() {
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 4f2fc86..bee66637 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -17,6 +17,7 @@
package com.android.server.vibrator;
import android.annotation.Nullable;
+import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibrationEffect;
import android.os.IBinder;
import android.os.PowerManager;
@@ -65,10 +66,13 @@
* IVibratorManager.CAP_MIXED_TRIGGER_*.
* @param vibratorIds The id of the vibrators to be prepared.
*/
- void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds);
+ boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds);
/** Callback triggered after synchronized vibrations were prepared. */
- void triggerSyncedVibration(long vibrationId);
+ boolean triggerSyncedVibration(long vibrationId);
+
+ /** Callback triggered to cancel a prepared synced vibration. */
+ void cancelSyncedVibration();
/** Callback triggered when vibration thread is complete. */
void onVibrationEnded(long vibrationId, Vibration.Status status);
@@ -146,6 +150,20 @@
}
}
+ /** Notify current vibration that a synced step has completed. */
+ public void syncedVibrationComplete() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
+ }
+ if (mCurrentVibrateStep != null) {
+ for (int i = 0; i < mVibrators.size(); i++) {
+ mCurrentVibrateStep.vibratorComplete(mVibrators.keyAt(i));
+ }
+ }
+ }
+ }
+
/** Notify current vibration that a step has completed on given vibrator. */
public void vibratorComplete(int vibratorId) {
synchronized (mLock) {
@@ -530,7 +548,7 @@
/** Represent a synchronized vibration step on multiple vibrators. */
private final class SyncedVibrateStep implements VibrateStep {
private final SparseArray<VibrationEffect> mEffects;
- private final int mRequiredCapabilities;
+ private final long mRequiredCapabilities;
private final int[] mVibratorIds;
@GuardedBy("mLock")
@@ -539,8 +557,7 @@
SyncedVibrateStep(SparseArray<VibrationEffect> effects) {
mEffects = effects;
mActiveVibratorCounter = mEffects.size();
- // TODO(b/159207608): Calculate required capabilities for syncing this step.
- mRequiredCapabilities = 0;
+ mRequiredCapabilities = calculateRequiredSyncCapabilities(effects);
mVibratorIds = new int[effects.size()];
for (int i = 0; i < effects.size(); i++) {
mVibratorIds[i] = effects.keyAt(i);
@@ -574,18 +591,21 @@
@Override
public Vibration.Status play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SyncedVibrateStep");
- long timeout = -1;
+ long duration = -1;
try {
if (DEBUG) {
Slog.d(TAG, "SyncedVibrateStep starting...");
}
final PriorityQueue<AmplitudeStep> nextSteps = new PriorityQueue<>(mEffects.size());
long startTime = SystemClock.uptimeMillis();
- mCallbacks.prepareSyncedVibration(mRequiredCapabilities, mVibratorIds);
- timeout = startVibrating(startTime, nextSteps);
- mCallbacks.triggerSyncedVibration(mVibration.id);
- noteVibratorOn(timeout);
+ duration = startVibratingSynced(startTime, nextSteps);
+ if (duration <= 0) {
+ // Vibrate step failed, vibrator could not be turned on for this step.
+ return Vibration.Status.IGNORED;
+ }
+
+ noteVibratorOn(duration);
while (!nextSteps.isEmpty()) {
AmplitudeStep step = nextSteps.poll();
if (!waitUntil(step.startTime)) {
@@ -607,7 +627,7 @@
synchronized (mLock) {
// All OneShot and Waveform effects have finished. Just wait for the other
// effects to end via native callbacks before finishing this synced step.
- final long wakeUpTime = startTime + timeout + CALLBACKS_EXTRA_TIMEOUT;
+ final long wakeUpTime = startTime + duration + CALLBACKS_EXTRA_TIMEOUT;
if (mActiveVibratorCounter <= 0 || waitForVibrationComplete(this, wakeUpTime)) {
return Vibration.Status.FINISHED;
}
@@ -617,7 +637,7 @@
return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
}
} finally {
- if (timeout > 0) {
+ if (duration > 0) {
noteVibratorOff();
}
if (DEBUG) {
@@ -628,14 +648,48 @@
}
/**
+ * Starts playing effects on designated vibrators, in sync.
+ *
+ * @return A positive duration, in millis, to wait for the completion of this effect.
+ * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
+ * returns the duration of a single run to be used as timeout for callbacks.
+ */
+ private long startVibratingSynced(long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
+ // This synchronization of vibrators should be executed one at a time, even if we are
+ // vibrating different sets of vibrators in parallel. The manager can only prepareSynced
+ // one set of vibrators at a time.
+ synchronized (mLock) {
+ boolean hasPrepared = false;
+ boolean hasTriggered = false;
+ try {
+ hasPrepared = mCallbacks.prepareSyncedVibration(mRequiredCapabilities,
+ mVibratorIds);
+ long timeout = startVibrating(startTime, nextSteps);
+
+ // Check if preparation was successful, otherwise devices area already vibrating
+ if (hasPrepared) {
+ hasTriggered = mCallbacks.triggerSyncedVibration(mVibration.id);
+ }
+ return timeout;
+ } finally {
+ if (hasPrepared && !hasTriggered) {
+ mCallbacks.cancelSyncedVibration();
+ return 0;
+ }
+ }
+ }
+ }
+
+ /**
* Starts playing effects on designated vibrators.
*
* <p>This includes the {@link VibrationEffect.OneShot} and {@link VibrationEffect.Waveform}
* effects, that should start in sync with all other effects in this step. The waveforms are
* controlled by {@link AmplitudeStep} added to the {@code nextSteps} queue.
*
- * @return A duration, in millis, to wait for the completion of all vibrations. This ignores
- * any repeating waveform duration and returns the duration of a single run.
+ * @return A positive duration, in millis, to wait for the completion of this effect.
+ * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
+ * returns the duration of a single run to be used as timeout for callbacks.
*/
private long startVibrating(long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
long maxDuration = 0;
@@ -651,9 +705,9 @@
/**
* Play a single effect on a single vibrator.
*
- * @return A duration, in millis, to wait for the completion of this effect. This ignores
- * any repeating waveform duration and returns the duration of a single run to be used as
- * timeout for callbacks.
+ * @return A positive duration, in millis, to wait for the completion of this effect.
+ * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
+ * returns the duration of a single run to be used as timeout for callbacks.
*/
private long startVibrating(VibratorController controller, VibrationEffect effect,
long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
@@ -698,6 +752,49 @@
}
}
}
+
+ /**
+ * Return all capabilities required from the {@link IVibratorManager} to prepare and
+ * trigger all given effects in sync.
+ *
+ * @return {@link IVibratorManager#CAP_SYNC} together with all required
+ * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
+ */
+ private long calculateRequiredSyncCapabilities(SparseArray<VibrationEffect> effects) {
+ long prepareCap = 0;
+ for (int i = 0; i < effects.size(); i++) {
+ VibrationEffect effect = effects.valueAt(i);
+ if (effect instanceof VibrationEffect.OneShot
+ || effect instanceof VibrationEffect.Waveform) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_ON;
+ } else if (effect instanceof VibrationEffect.Prebaked) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
+ } else if (effect instanceof VibrationEffect.Composed) {
+ prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
+ }
+ }
+ int triggerCap = 0;
+ if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) {
+ triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON;
+ }
+ if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) {
+ triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
+ }
+ if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) {
+ triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
+ }
+ return IVibratorManager.CAP_SYNC | prepareCap | triggerCap;
+ }
+
+ /**
+ * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with
+ * different ones, requiring a mixed trigger capability from the vibrator manager for
+ * syncing all effects.
+ */
+ private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) {
+ return (prepareCapabilities & capability) != 0
+ && (prepareCapabilities & ~capability) != 0;
+ }
}
/** Represent a step to set amplitude on a single vibrator. */
@@ -794,12 +891,12 @@
// Waveform has ended, no more steps to run.
return null;
}
- long nextWakeUpTime = startTime + waveform.getTimings()[currentIndex];
+ long nextStartTime = startTime + waveform.getTimings()[currentIndex];
int nextIndex = currentIndex + 1;
if (nextIndex >= waveform.getTimings().length) {
nextIndex = waveform.getRepeatIndex();
}
- return new AmplitudeStep(vibratorId, waveform, nextIndex, nextWakeUpTime,
+ return new AmplitudeStep(vibratorId, waveform, nextIndex, nextStartTime,
nextVibratorStopTime());
}
diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_VibratorManagerService.cpp
index 71de9bd..5dbb71a 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.cpp
+++ b/services/core/jni/com_android_server_VibratorManagerService.cpp
@@ -24,27 +24,47 @@
#include <utils/Log.h>
#include <utils/misc.h>
-#include <vibratorservice/VibratorManagerHalWrapper.h>
+#include <vibratorservice/VibratorManagerHalController.h>
#include "com_android_server_VibratorManagerService.h"
namespace android {
+static JavaVM* sJvm = nullptr;
+static jmethodID sMethodIdOnComplete;
static std::mutex gManagerMutex;
-static vibrator::ManagerHalWrapper* gManager GUARDED_BY(gManagerMutex) = nullptr;
+static vibrator::ManagerHalController* gManager GUARDED_BY(gManagerMutex) = nullptr;
class NativeVibratorManagerService {
public:
- NativeVibratorManagerService() : mHal(std::make_unique<vibrator::LegacyManagerHalWrapper>()) {}
- ~NativeVibratorManagerService() = default;
+ NativeVibratorManagerService(JNIEnv* env, jobject callbackListener)
+ : mHal(std::make_unique<vibrator::ManagerHalController>()),
+ mCallbackListener(env->NewGlobalRef(callbackListener)) {
+ LOG_ALWAYS_FATAL_IF(mHal == nullptr, "Unable to find reference to vibrator manager hal");
+ LOG_ALWAYS_FATAL_IF(mCallbackListener == nullptr,
+ "Unable to create global reference to vibration callback handler");
+ }
- vibrator::ManagerHalWrapper* hal() const { return mHal.get(); }
+ ~NativeVibratorManagerService() {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ jniEnv->DeleteGlobalRef(mCallbackListener);
+ }
+
+ vibrator::ManagerHalController* hal() const { return mHal.get(); }
+
+ std::function<void()> createCallback(jlong vibrationId) {
+ return [vibrationId, this]() {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, vibrationId);
+ };
+ }
private:
- const std::unique_ptr<vibrator::ManagerHalWrapper> mHal;
+ const std::unique_ptr<vibrator::ManagerHalController> mHal;
+ const jobject mCallbackListener;
};
-vibrator::ManagerHalWrapper* android_server_VibratorManagerService_getManager() {
+vibrator::ManagerHalController* android_server_VibratorManagerService_getManager() {
std::lock_guard<std::mutex> lock(gManagerMutex);
return gManager;
}
@@ -58,9 +78,9 @@
}
}
-static jlong nativeInit(JNIEnv* /* env */, jclass /* clazz */) {
+static jlong nativeInit(JNIEnv* env, jclass /* clazz */, jobject callbackListener) {
std::unique_ptr<NativeVibratorManagerService> service =
- std::make_unique<NativeVibratorManagerService>();
+ std::make_unique<NativeVibratorManagerService>(env, callbackListener);
{
std::lock_guard<std::mutex> lock(gManagerMutex);
gManager = service->hal();
@@ -72,6 +92,17 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeService));
}
+static jlong nativeGetCapabilities(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeGetCapabilities failed because native service was not initialized");
+ return 0;
+ }
+ auto result = service->hal()->getCapabilities();
+ return result.isOk() ? static_cast<jlong>(result.value()) : 0;
+}
+
static jintArray nativeGetVibratorIds(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
NativeVibratorManagerService* service =
reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
@@ -89,13 +120,61 @@
return ids;
}
+static jboolean nativePrepareSynced(JNIEnv* env, jclass /* clazz */, jlong servicePtr,
+ jintArray vibratorIds) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativePrepareSynced failed because native service was not initialized");
+ return JNI_FALSE;
+ }
+ jsize size = env->GetArrayLength(vibratorIds);
+ std::vector<int32_t> ids(size);
+ env->GetIntArrayRegion(vibratorIds, 0, size, reinterpret_cast<jint*>(ids.data()));
+ return service->hal()->prepareSynced(ids).isOk() ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean nativeTriggerSynced(JNIEnv* env, jclass /* clazz */, jlong servicePtr,
+ jlong vibrationId) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeTriggerSynced failed because native service was not initialized");
+ return JNI_FALSE;
+ }
+ auto callback = service->createCallback(vibrationId);
+ return service->hal()->triggerSynced(callback).isOk() ? JNI_TRUE : JNI_FALSE;
+}
+
+static void nativeCancelSynced(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeCancelSynced failed because native service was not initialized");
+ return;
+ }
+ service->hal()->cancelSynced();
+}
+
static const JNINativeMethod method_table[] = {
- {"nativeInit", "()J", (void*)nativeInit},
+ {"nativeInit",
+ "(Lcom/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener;)J",
+ (void*)nativeInit},
{"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
+ {"nativeGetCapabilities", "(J)J", (void*)nativeGetCapabilities},
{"nativeGetVibratorIds", "(J)[I", (void*)nativeGetVibratorIds},
+ {"nativePrepareSynced", "(J[I)Z", (void*)nativePrepareSynced},
+ {"nativeTriggerSynced", "(JJ)Z", (void*)nativeTriggerSynced},
+ {"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced},
};
-int register_android_server_VibratorManagerService(JNIEnv* env) {
+int register_android_server_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
+ sJvm = jvm;
+ auto listenerClassName =
+ "com/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener";
+ jclass listenerClass = FindClassOrDie(env, listenerClassName);
+ sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V");
+
return jniRegisterNativeMethods(env, "com/android/server/VibratorManagerService", method_table,
NELEM(method_table));
}
diff --git a/services/core/jni/com_android_server_VibratorManagerService.h b/services/core/jni/com_android_server_VibratorManagerService.h
index 3f2a322..22950c5 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.h
+++ b/services/core/jni/com_android_server_VibratorManagerService.h
@@ -17,11 +17,11 @@
#ifndef _ANDROID_SERVER_VIBRATOR_MANAGER_SERVICE_H
#define _ANDROID_SERVER_VIBRATOR_MANAGER_SERVICE_H
-#include <vibratorservice/VibratorManagerHalWrapper.h>
+#include <vibratorservice/VibratorManagerHalController.h>
namespace android {
-extern vibrator::ManagerHalWrapper* android_server_VibratorManagerService_getManager();
+extern vibrator::ManagerHalController* android_server_VibratorManagerService_getManager();
} // namespace android
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 7ed3748..4e47984 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -77,7 +77,7 @@
if (vibratorId < 0) {
return std::move(std::make_unique<vibrator::HalController>());
}
- vibrator::ManagerHalWrapper* manager = android_server_VibratorManagerService_getManager();
+ vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager();
if (manager == nullptr) {
return nullptr;
}
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c5394f3..34f6048 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -39,7 +39,7 @@
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_vr_VrManagerService(JNIEnv* env);
int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
-int register_android_server_VibratorManagerService(JNIEnv* env);
+int register_android_server_VibratorManagerService(JavaVM* vm, JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
@@ -90,7 +90,7 @@
register_android_server_UsbHostManager(env);
register_android_server_vr_VrManagerService(env);
register_android_server_vibrator_VibratorController(vm, env);
- register_android_server_VibratorManagerService(env);
+ register_android_server_VibratorManagerService(vm, env);
register_android_server_SystemServer(env);
register_android_server_location_GnssLocationProvider(env);
register_android_server_devicepolicy_CryptoTestHelper(env);
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 7a0cb8e..31e2b64 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -61,7 +61,7 @@
libs: [
"android.hardware.power-V1-java",
"android.hardware.tv.cec-V1.0-java",
- "android.hardware.vibrator-V1-java",
+ "android.hardware.vibrator-V2-java",
"android.hidl.manager-V1.0-java",
"android.test.mock",
"android.test.base",
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index f7b2492..f0d7006 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -32,6 +33,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -44,6 +46,7 @@
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.vibrator.IVibrator;
+import android.hardware.vibrator.IVibratorManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.CombinedVibrationEffect;
@@ -204,7 +207,7 @@
public void createService_initializesNativeManagerServiceAndVibrators() {
mockVibrators(1, 2);
createService();
- verify(mNativeWrapperMock).init();
+ verify(mNativeWrapperMock).init(any());
assertTrue(mVibratorProviders.get(1).isInitialized());
assertTrue(mVibratorProviders.get(2).isInitialized());
}
@@ -557,8 +560,6 @@
@Test
public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
mockVibrators(1);
- mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
- IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createService();
// The native callback will be dispatched manually in this test.
@@ -577,6 +578,139 @@
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
}
+ @Test
+ public void vibrate_withTriggerCallback_finishesVibration() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
+ mockVibrators(1, 2);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ VibratorManagerService service = createService();
+ // The native callback will be dispatched manually in this test.
+ mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+
+ ArgumentCaptor<VibratorManagerService.OnSyncedVibrationCompleteListener> listenerCaptor =
+ ArgumentCaptor.forClass(
+ VibratorManagerService.OnSyncedVibrationCompleteListener.class);
+ verify(mNativeWrapperMock).init(listenerCaptor.capture());
+
+ // Mock trigger callback on registered listener.
+ when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
+ when(mNativeWrapperMock.triggerSynced(anyLong())).then(answer -> {
+ listenerCaptor.getValue().onComplete(answer.getArgument(0));
+ return true;
+ });
+
+ VibrationEffect composed = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
+ .compose();
+ CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(composed);
+
+ // Wait for vibration to start, it should finish right away with trigger callback.
+ vibrate(service, effect, ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until callback is triggered.
+ assertTrue(waitUntil(s -> !listenerCaptor.getAllValues().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
+ verify(mNativeWrapperMock).triggerSynced(anyLong());
+ assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects());
+ assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+ }
+
+ @Test
+ public void vibrate_withMultipleVibratorsAndCapabilities_prepareAndTriggerCalled()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_PERFORM,
+ IVibratorManager.CAP_PREPARE_COMPOSE, IVibratorManager.CAP_MIXED_TRIGGER_PERFORM,
+ IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE);
+ mockVibrators(1, 2);
+ when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
+ when(mNativeWrapperMock.triggerSynced(anyLong())).thenReturn(true);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ VibratorManagerService service = createService();
+
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2, VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose())
+ .combine();
+ vibrate(service, effect, ALARM_ATTRS);
+ assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
+ verify(mNativeWrapperMock).triggerSynced(anyLong());
+ verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
+ }
+
+ @Test
+ public void vibrate_withMultipleVibratorsWithoutCapabilities_skipPrepareAndTrigger()
+ throws Exception {
+ // Missing CAP_MIXED_TRIGGER_ON and CAP_MIXED_TRIGGER_PERFORM.
+ mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON,
+ IVibratorManager.CAP_PREPARE_PERFORM);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createService();
+
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+ .combine();
+ vibrate(service, effect, ALARM_ATTRS);
+ assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ verify(mNativeWrapperMock, never()).prepareSynced(any());
+ verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
+ verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
+ }
+
+ @Test
+ public void vibrate_withMultipleVibratorsPrepareFailed_skipTrigger() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON);
+ mockVibrators(1, 2);
+ when(mNativeWrapperMock.prepareSynced(any())).thenReturn(false);
+ VibratorManagerService service = createService();
+
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.createOneShot(10, 50))
+ .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+ .combine();
+ vibrate(service, effect, ALARM_ATTRS);
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
+ verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
+ verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
+ }
+
+ @Test
+ public void vibrate_withMultipleVibratorsTriggerFailed_cancelPreparedSynced() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON);
+ mockVibrators(1, 2);
+ when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
+ when(mNativeWrapperMock.triggerSynced(anyLong())).thenReturn(false);
+ VibratorManagerService service = createService();
+
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.createOneShot(10, 50))
+ .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+ .combine();
+ vibrate(service, effect, ALARM_ATTRS);
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
+ verify(mNativeWrapperMock).triggerSynced(anyLong());
+ verify(mNativeWrapperMock, times(2)).cancelSynced(); // Trigger on service creation too.
+ }
@Test
public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
@@ -682,6 +816,11 @@
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
}
+ private void mockCapabilities(long... capabilities) {
+ when(mNativeWrapperMock.getCapabilities()).thenReturn(
+ Arrays.stream(capabilities).reduce(0, (a, b) -> a | b));
+ }
+
private void mockVibrators(int... vibratorIds) {
for (int vibratorId : vibratorIds) {
mVibratorProviders.put(vibratorId,
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 7d20879..3ff8e76 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -27,8 +28,10 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.hardware.vibrator.IVibrator;
+import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibrationEffect;
import android.os.IBinder;
import android.os.PowerManager;
@@ -388,6 +391,20 @@
}
@Test
+ public void vibrate_singleVibrator_skipsSyncedCallbacks() {
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ long vibrationId = 1;
+ waitForCompletion(startThreadAndDispatcher(vibrationId++,
+ VibrationEffect.createOneShot(10, 100)));
+
+ verify(mThreadCallbacks).onVibrationEnded(anyLong(), eq(Vibration.Status.FINISHED));
+ verify(mThreadCallbacks, never()).prepareSyncedVibration(anyLong(), any());
+ verify(mThreadCallbacks, never()).triggerSyncedVibration(anyLong());
+ verify(mThreadCallbacks, never()).cancelSyncedVibration();
+ }
+
+ @Test
public void vibrate_multipleExistingAndMissingVibrators_vibratesOnlyExistingOnes()
throws Exception {
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_TICK);
@@ -484,8 +501,7 @@
}
@Test
- public void vibrate_multipleSequential_runsVibrationInOrderWithDelays()
- throws Exception {
+ public void vibrate_multipleSequential_runsVibrationInOrderWithDelays() throws Exception {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -529,6 +545,126 @@
}
@Test
+ public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
+ int[] vibratorIds = new int[]{1, 2};
+ long vibrationId = 1;
+ mockVibrators(vibratorIds);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ when(mThreadCallbacks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
+ when(mThreadCallbacks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+
+ VibrationEffect composed = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
+ .compose();
+ CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(composed);
+ VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+
+ assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffects().isEmpty()
+ && !mVibratorProviders.get(2).getEffects().isEmpty(), thread, TEST_TIMEOUT_MILLIS));
+ thread.syncedVibrationComplete();
+ waitForCompletion(thread);
+
+ long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_COMPOSE;
+ verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+ verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId));
+ verify(mThreadCallbacks, never()).cancelSyncedVibration();
+ verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
+
+ assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects());
+ assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+ }
+
+ @Test
+ public void vibrate_multipleSynced_callsPrepareAndTriggerCallbacks() {
+ int[] vibratorIds = new int[]{1, 2, 3, 4};
+ mockVibrators(vibratorIds);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
+ when(mThreadCallbacks.triggerSyncedVibration(anyLong())).thenReturn(true);
+
+ long vibrationId = 1;
+ VibrationEffect composed = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose();
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+ .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
+ .addVibrator(4, composed)
+ .combine();
+ VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+ waitForCompletion(thread);
+
+ long expectedCap = IVibratorManager.CAP_SYNC
+ | IVibratorManager.CAP_PREPARE_ON
+ | IVibratorManager.CAP_PREPARE_PERFORM
+ | IVibratorManager.CAP_PREPARE_COMPOSE
+ | IVibratorManager.CAP_MIXED_TRIGGER_ON
+ | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM
+ | IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
+ verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+ verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId));
+ verify(mThreadCallbacks, never()).cancelSyncedVibration();
+ verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
+ }
+
+ @Test
+ public void vibrate_multipleSyncedPrepareFailed_skipTriggerStepAndVibrates() {
+ int[] vibratorIds = new int[]{1, 2};
+ mockVibrators(vibratorIds);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(false);
+
+ long vibrationId = 1;
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.createOneShot(10, 100))
+ .addVibrator(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{200}, -1))
+ .combine();
+ VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+ waitForCompletion(thread);
+
+ long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON;
+ verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+ verify(mThreadCallbacks, never()).triggerSyncedVibration(eq(vibrationId));
+ verify(mThreadCallbacks, never()).cancelSyncedVibration();
+
+ assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects());
+ assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes());
+ assertEquals(Arrays.asList(expectedOneShot(5)), mVibratorProviders.get(2).getEffects());
+ assertEquals(Arrays.asList(200), mVibratorProviders.get(2).getAmplitudes());
+ }
+
+ @Test
+ public void vibrate_multipleSyncedTriggerFailed_cancelPreparedVibrationAndSkipSetAmplitude() {
+ int[] vibratorIds = new int[]{1, 2};
+ mockVibrators(vibratorIds);
+ mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
+ when(mThreadCallbacks.triggerSyncedVibration(anyLong())).thenReturn(false);
+
+ long vibrationId = 1;
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.createOneShot(10, 100))
+ .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .combine();
+ VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+ waitForCompletion(thread);
+
+ long expectedCap = IVibratorManager.CAP_SYNC
+ | IVibratorManager.CAP_PREPARE_ON
+ | IVibratorManager.CAP_PREPARE_PERFORM
+ | IVibratorManager.CAP_MIXED_TRIGGER_ON
+ | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
+ verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+ verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId));
+ verify(mThreadCallbacks).cancelSyncedVibration();
+ assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty());
+ }
+
+ @Test
public void vibrate_multipleWaveforms_playsWaveformsInParallel() throws Exception {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -623,6 +759,7 @@
assertTrue(waitUntil(t -> t.getVibrators().get(2).isVibrating(), vibrationThread,
TEST_TIMEOUT_MILLIS));
+ assertTrue(vibrationThread.isAlive());
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.