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:
Lais Andrade
2020-11-17 20:29:03 +00:00
parent 02151de444
commit c4f27f3020
11 changed files with 589 additions and 58 deletions

View File

@@ -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",

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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());
}

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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",

View File

@@ -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,

View File

@@ -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.