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
This commit is contained in:
@@ -103,7 +103,7 @@ java_library_static {
|
||||
"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",
|
||||
|
||||
@@ -111,6 +111,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
// 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
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,18 +906,33 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
mServiceRef = new WeakReference<>(service);
|
||||
}
|
||||
|
||||
@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();
|
||||
@@ -952,9 +1005,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,11 +119,17 @@ public class VibratorService extends IVibratorService.Stub {
|
||||
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
|
||||
|
||||
@@ -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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
* 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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
/** 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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
@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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
|
||||
}
|
||||
} finally {
|
||||
if (timeout > 0) {
|
||||
if (duration > 0) {
|
||||
noteVibratorOff();
|
||||
}
|
||||
if (DEBUG) {
|
||||
@@ -627,6 +647,39 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -634,8 +687,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
* 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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
/**
|
||||
* 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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
|
||||
// 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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 void destroyNativeService(void* ptr) {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
|
||||
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 @@ static jintArray nativeGetVibratorIds(JNIEnv* env, jclass /* clazz */, jlong ser
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ int register_android_server_UsbMidiDevice(JNIEnv* env);
|
||||
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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
|
||||
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);
|
||||
|
||||
@@ -61,7 +61,7 @@ android_test {
|
||||
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",
|
||||
|
||||
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertNull;
|
||||
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.inOrder;
|
||||
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.content.pm.PackageManagerInternal;
|
||||
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 class VibratorManagerServiceTest {
|
||||
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 @@ public class VibratorManagerServiceTest {
|
||||
@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 @@ public class VibratorManagerServiceTest {
|
||||
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 @@ public class VibratorManagerServiceTest {
|
||||
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,
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.vibrator;
|
||||
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.doAnswer;
|
||||
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;
|
||||
@@ -387,6 +390,20 @@ public class VibrationThreadTest {
|
||||
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
|
||||
}
|
||||
|
||||
@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 {
|
||||
@@ -484,8 +501,7 @@ public class VibrationThreadTest {
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -528,6 +544,126 @@ public class VibrationThreadTest {
|
||||
mVibratorProviders.get(3).getEffects());
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -623,6 +759,7 @@ public class VibrationThreadTest {
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user