From 9e755fb3fe4ac8fdd6b86387daeebed28529dd24 Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Mon, 16 Nov 2020 20:02:19 +0000 Subject: [PATCH] Implement setAlwaysOn to IVibratorManagerService This implementation supports synced CombinedVibrationEffect instances with prebaked effects only. This implementation adds some vibration checks from VibratorService to VibratorManager, and wires the implementation of VibratorController native service to IVibratorManager. Bug: 167946816 Bug: 131311651 Test: atest FrameworksServiceTests:VibratorManagerServiceTest Change-Id: I8da10c7903e91bba30b11fe46515121858fb5211 --- .../android/os/IVibratorManagerService.aidl | 2 + core/java/android/os/IVibratorService.aidl | 2 - core/java/android/os/SystemVibrator.java | 11 +- .../server/VibratorManagerService.java | 417 +++++++++++++++++- .../com/android/server/VibratorService.java | 73 +-- .../android/server/vibrator/Vibration.java | 2 +- ..._android_server_VibratorManagerService.cpp | 16 + ...om_android_server_VibratorManagerService.h | 28 ++ ...oid_server_vibrator_VibratorController.cpp | 21 +- .../java/com/android/server/SystemServer.java | 4 + .../server/VibratorManagerServiceTest.java | 200 ++++++++- .../android/server/VibratorServiceTest.java | 37 -- 12 files changed, 682 insertions(+), 131 deletions(-) create mode 100644 services/core/jni/com_android_server_VibratorManagerService.h diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl index 08d201977c49c..b591f6016d14c 100644 --- a/core/java/android/os/IVibratorManagerService.aidl +++ b/core/java/android/os/IVibratorManagerService.aidl @@ -22,6 +22,8 @@ import android.os.VibrationAttributes; /** {@hide} */ interface IVibratorManagerService { int[] getVibratorIds(); + boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + in CombinedVibrationEffect effect, in VibrationAttributes attributes); void vibrate(int uid, String opPkg, in CombinedVibrationEffect effect, in VibrationAttributes attributes, String reason, IBinder token); void cancelVibrate(IBinder token); diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl index 615ae65933304..562ac16099ddc 100644 --- a/core/java/android/os/IVibratorService.aidl +++ b/core/java/android/os/IVibratorService.aidl @@ -30,8 +30,6 @@ interface IVibratorService boolean hasAmplitudeControl(); int[] areEffectsSupported(in int[] effectIds); boolean[] arePrimitivesSupported(in int[] primitiveIds); - boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect, - in VibrationAttributes attributes); void vibrate(int uid, String opPkg, in VibrationEffect effect, in VibrationAttributes attributes, String reason, IBinder token); void cancelVibrate(IBinder token); diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 5c9067ac3c3e5..b742ee2be43bf 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -38,6 +38,7 @@ public class SystemVibrator extends Vibrator { private static final String TAG = "Vibrator"; private final IVibratorService mService; + private final IVibratorManagerService mManagerService; private final Binder mToken = new Binder(); private final Context mContext; @@ -49,6 +50,8 @@ public class SystemVibrator extends Vibrator { public SystemVibrator() { mContext = null; mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator")); + mManagerService = IVibratorManagerService.Stub.asInterface( + ServiceManager.getService("vibrator_manager")); } @UnsupportedAppUsage @@ -56,6 +59,8 @@ public class SystemVibrator extends Vibrator { super(context); mContext = context; mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator")); + mManagerService = IVibratorManagerService.Stub.asInterface( + ServiceManager.getService("vibrator_manager")); } @Override @@ -207,13 +212,14 @@ public class SystemVibrator extends Vibrator { @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, AudioAttributes attributes) { - if (mService == null) { + if (mManagerService == null) { Log.w(TAG, "Failed to set always-on effect; no vibrator service."); return false; } try { VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build(); - return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, atr); + CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect); + return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr); } catch (RemoteException e) { Log.w(TAG, "Failed to set always-on effect.", e); } @@ -255,7 +261,6 @@ public class SystemVibrator extends Vibrator { } } - @Override public void cancel() { if (mService == null) { diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java index f1f2815be8fa7..57ab0471b343c 100644 --- a/services/core/java/com/android/server/VibratorManagerService.java +++ b/services/core/java/com/android/server/VibratorManagerService.java @@ -16,32 +16,89 @@ package com.android.server; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.vibrator.IVibrator; import android.os.CombinedVibrationEffect; +import android.os.Handler; import android.os.IBinder; import android.os.IVibratorManagerService; +import android.os.Looper; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; +import android.os.Trace; import android.os.VibrationAttributes; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.util.Slog; +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.vibrator.Vibration; +import com.android.server.vibrator.VibrationScaler; +import com.android.server.vibrator.VibrationSettings; +import com.android.server.vibrator.VibratorController; import libcore.util.NativeAllocationRegistry; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.Arrays; +import java.util.function.Consumer; +import java.util.function.Function; /** System implementation of {@link IVibratorManagerService}. */ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String TAG = "VibratorManagerService"; private static final boolean DEBUG = false; + private static final VibrationAttributes DEFAULT_ATTRIBUTES = + new VibrationAttributes.Builder().build(); + /** Lifecycle responsible for initializing this class at the right system server phases. */ + public static class Lifecycle extends SystemService { + private VibratorManagerService mService; + + public Lifecycle(Context context) { + super(context); + } + + @Override + public void onStart() { + mService = new VibratorManagerService(getContext(), new Injector()); + publishBinderService("vibrator_manager", mService); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + mService.systemReady(); + } + } + } + + private final Object mLock = new Object(); private final Context mContext; + private final Handler mHandler; + private final AppOpsManager mAppOps; private final NativeWrapper mNativeWrapper; private final int[] mVibratorIds; + private final SparseArray mVibrators; + @GuardedBy("mLock") + private final SparseArray mAlwaysOnEffects = new SparseArray<>(); + + private VibrationSettings mVibrationSettings; + private VibrationScaler mVibrationScaler; + @GuardedBy("mLock") + private boolean mLowPowerMode; static native long nativeInit(); @@ -49,19 +106,63 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { static native int[] nativeGetVibratorIds(long nativeServicePtr); - VibratorManagerService(Context context) { - this(context, new Injector()); - } - @VisibleForTesting VibratorManagerService(Context context, Injector injector) { mContext = context; + mHandler = injector.createHandler(Looper.myLooper()); mNativeWrapper = injector.getNativeWrapper(); - mNativeWrapper.init(); + mAppOps = mContext.getSystemService(AppOpsManager.class); + int[] vibratorIds = mNativeWrapper.getVibratorIds(); - mVibratorIds = vibratorIds == null ? new int[0] : vibratorIds; + if (vibratorIds == null) { + mVibratorIds = new int[0]; + mVibrators = new SparseArray<>(0); + } else { + // 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)); + } + } + } + + /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */ + @VisibleForTesting + void systemReady() { + Slog.v(TAG, "Initializing VibratorManager service..."); + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "systemReady"); + try { + mVibrationSettings = new VibrationSettings(mContext, mHandler); + mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); + + mVibrationSettings.addListener(this::updateServiceState); + + PowerManagerInternal pm = LocalServices.getService(PowerManagerInternal.class); + pm.registerLowPowerModeObserver( + new PowerManagerInternal.LowPowerModeListener() { + @Override + public int getServiceType() { + return PowerManager.ServiceType.VIBRATION; + } + + @Override + public void onLowPowerModeChanged(PowerSaveState result) { + synchronized (mLock) { + mLowPowerMode = result.batterySaverEnabled; + } + updateServiceState(); + } + }); + + updateServiceState(); + } finally { + Slog.v(TAG, "VibratorManager service initialized"); + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } } @Override // Binder call @@ -70,7 +171,48 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override // Binder call - public void vibrate(int uid, String opPkg, CombinedVibrationEffect effect, + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attrs) { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setAlwaysOnEffect"); + try { + if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) { + throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission"); + } + if (effect == null) { + synchronized (mLock) { + mAlwaysOnEffects.delete(alwaysOnId); + onAllVibratorsLocked(v -> { + if (v.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { + v.updateAlwaysOn(alwaysOnId, /* effect= */ null); + } + }); + } + return true; + } + if (!isEffectValid(effect)) { + return false; + } + attrs = fixupVibrationAttributes(attrs); + synchronized (mLock) { + SparseArray effects = fixupAlwaysOnEffectsLocked(effect); + if (effects == null) { + // Invalid effects set in CombinedVibrationEffect, or always-on capability is + // missing on individual vibrators. + return false; + } + AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration( + alwaysOnId, uid, opPkg, attrs, effects); + mAlwaysOnEffects.put(alwaysOnId, alwaysOnVibration); + updateAlwaysOnLocked(alwaysOnVibration); + } + return true; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + @Override // Binder call + public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect, @Nullable VibrationAttributes attrs, String reason, IBinder token) { throw new UnsupportedOperationException("Not implemented"); } @@ -86,6 +228,214 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver); } + private void updateServiceState() { + synchronized (mLock) { + for (int i = 0; i < mAlwaysOnEffects.size(); i++) { + updateAlwaysOnLocked(mAlwaysOnEffects.valueAt(i)); + } + } + } + + @GuardedBy("mLock") + private void updateAlwaysOnLocked(AlwaysOnVibration vib) { + for (int i = 0; i < vib.effects.size(); i++) { + VibratorController vibrator = mVibrators.get(vib.effects.keyAt(i)); + VibrationEffect.Prebaked effect = vib.effects.valueAt(i); + if (vibrator == null) { + continue; + } + Vibration.Status ignoredStatus = shouldIgnoreVibrationLocked( + vib.uid, vib.opPkg, vib.attrs); + if (ignoredStatus == null) { + effect = mVibrationScaler.scale(effect, vib.attrs.getUsage()); + } else { + // Vibration should not run, use null effect to remove registered effect. + effect = null; + } + vibrator.updateAlwaysOn(vib.alwaysOnId, effect); + } + } + + /** + * Check if a vibration with given {@code uid}, {@code opPkg} and {@code attrs} should be + * ignored by this service. + * + * @param uid The user id of this vibration + * @param opPkg The package name of this vibration + * @param attrs The attributes of this vibration + * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored. + */ + @GuardedBy("mLock") + @Nullable + private Vibration.Status shouldIgnoreVibrationLocked(int uid, String opPkg, + VibrationAttributes attrs) { + if (!shouldVibrateForPowerModeLocked(attrs)) { + return Vibration.Status.IGNORED_FOR_POWER; + } + + int intensity = mVibrationSettings.getCurrentIntensity(attrs.getUsage()); + if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { + return Vibration.Status.IGNORED_FOR_SETTINGS; + } + + if (attrs.getUsage() == VibrationAttributes.USAGE_RINGTONE + && !mVibrationSettings.shouldVibrateForRingtone()) { + if (DEBUG) { + Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones"); + } + return Vibration.Status.IGNORED_RINGTONE; + } + + int mode = getAppOpMode(uid, opPkg, attrs); + if (mode != AppOpsManager.MODE_ALLOWED) { + if (mode == AppOpsManager.MODE_ERRORED) { + // We might be getting calls from within system_server, so we don't actually + // want to throw a SecurityException here. + Slog.w(TAG, "Would be an error: vibrate from uid " + uid); + return Vibration.Status.IGNORED_ERROR_APP_OPS; + } else { + return Vibration.Status.IGNORED_APP_OPS; + } + } + + return null; + } + + /** Return true is current power mode allows this vibration to happen. */ + @GuardedBy("mLock") + private boolean shouldVibrateForPowerModeLocked(VibrationAttributes attrs) { + if (!mLowPowerMode) { + return true; + } + int usage = attrs.getUsage(); + return usage == VibrationAttributes.USAGE_RINGTONE + || usage == VibrationAttributes.USAGE_ALARM + || usage == VibrationAttributes.USAGE_COMMUNICATION_REQUEST; + } + + /** + * Check which mode should be set for a vibration with given {@code uid}, {@code opPkg} and + * {@code attrs}. This will return one of the AppOpsManager.MODE_*. + */ + private int getAppOpMode(int uid, String opPkg, VibrationAttributes attrs) { + int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE, + attrs.getAudioUsage(), uid, opPkg); + if (mode == AppOpsManager.MODE_ALLOWED) { + mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg); + } + if (mode == AppOpsManager.MODE_IGNORED + && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { + // If we're just ignoring the vibration op then this is set by DND and we should ignore + // if we're asked to bypass. AppOps won't be able to record this operation, so make + // sure we at least note it in the logs for debugging. + Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid); + mode = AppOpsManager.MODE_ALLOWED; + } + return mode; + } + + /** + * Validate the incoming {@link CombinedVibrationEffect}. + * + * We can't throw exceptions here since we might be called from some system_server component, + * which would bring the whole system down. + * + * @return whether the CombinedVibrationEffect is non-null and valid + */ + private static boolean isEffectValid(@Nullable CombinedVibrationEffect effect) { + if (effect == null) { + Slog.wtf(TAG, "effect must not be null"); + return false; + } + try { + effect.validate(); + } catch (Exception e) { + Slog.wtf(TAG, "Encountered issue when verifying CombinedVibrationEffect.", e); + return false; + } + return true; + } + + /** + * Return new {@link VibrationAttributes} that only applies flags that this user has permissions + * to use. + */ + private VibrationAttributes fixupVibrationAttributes(@Nullable VibrationAttributes attrs) { + if (attrs == null) { + attrs = DEFAULT_ATTRIBUTES; + } + if (attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { + if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { + final int flags = attrs.getFlags() + & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; + attrs = new VibrationAttributes.Builder(attrs) + .setFlags(flags, attrs.getFlags()).build(); + } + } + return attrs; + } + + @GuardedBy("mLock") + @Nullable + private SparseArray fixupAlwaysOnEffectsLocked( + CombinedVibrationEffect effect) { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked"); + try { + SparseArray effects; + if (effect instanceof CombinedVibrationEffect.Mono) { + VibrationEffect syncedEffect = ((CombinedVibrationEffect.Mono) effect).getEffect(); + effects = transformAllVibratorsLocked(unused -> syncedEffect); + } else if (effect instanceof CombinedVibrationEffect.Stereo) { + effects = ((CombinedVibrationEffect.Stereo) effect).getEffects(); + } else { + // Only synced combinations can be used for always-on effects. + return null; + } + SparseArray result = new SparseArray<>(); + for (int i = 0; i < effects.size(); i++) { + VibrationEffect prebaked = effects.valueAt(i); + if (!(prebaked instanceof VibrationEffect.Prebaked)) { + Slog.e(TAG, "Only prebaked effects supported for always-on."); + return null; + } + int vibratorId = effects.keyAt(i); + VibratorController vibrator = mVibrators.get(vibratorId); + if (vibrator != null && vibrator.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { + result.put(vibratorId, (VibrationEffect.Prebaked) prebaked); + } + } + if (result.size() == 0) { + return null; + } + return result; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + private boolean hasPermission(String permission) { + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } + + @GuardedBy("mLock") + private void onAllVibratorsLocked(Consumer consumer) { + for (int i = 0; i < mVibrators.size(); i++) { + consumer.accept(mVibrators.valueAt(i)); + } + } + + @GuardedBy("mLock") + private SparseArray transformAllVibratorsLocked(Function fn) { + SparseArray ret = new SparseArray<>(mVibrators.size()); + for (int i = 0; i < mVibrators.size(); i++) { + ret.put(mVibrators.keyAt(i), fn.apply(mVibrators.valueAt(i))); + } + return ret; + } + /** Point of injection for test dependencies */ @VisibleForTesting static class Injector { @@ -93,6 +443,57 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { NativeWrapper getNativeWrapper() { return new NativeWrapper(); } + + Handler createHandler(Looper looper) { + return new Handler(looper); + } + + VibratorController createVibratorController(int vibratorId, + VibratorController.OnVibrationCompleteListener listener) { + return new VibratorController(vibratorId, listener); + } + } + + /** + * Implementation of {@link VibratorController.OnVibrationCompleteListener} with a weak + * reference to this service. + */ + private static final class VibrationCompleteListener implements + VibratorController.OnVibrationCompleteListener { + private WeakReference mServiceRef; + + VibrationCompleteListener(VibratorManagerService service) { + mServiceRef = new WeakReference<>(service); + } + + @Override + public void onComplete(int vibratorId, long vibrationId) { + VibratorManagerService service = mServiceRef.get(); + if (service != null) { + // TODO(b/159207608): finish vibration if all vibrators finished for this vibration + } + } + } + + /** + * Combination of prekabed vibrations on multiple vibrators, with the same {@link + * VibrationAttributes}, that can be set for always-on effects. + */ + private static final class AlwaysOnVibration { + public final int alwaysOnId; + public final int uid; + public final String opPkg; + public final VibrationAttributes attrs; + public final SparseArray effects; + + AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs, + SparseArray effects) { + this.alwaysOnId = alwaysOnId; + this.uid = uid; + this.opPkg = opPkg; + this.attrs = attrs; + this.effects = effects; + } } /** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */ @@ -120,7 +521,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } - /** Provides limited functionality from {@link VibratorManagerService} as shell commands. */ + /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */ private final class VibratorManagerShellCommand extends ShellCommand { private final IBinder mToken; diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index db2b4e4edd2bc..bede1563655a1 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -73,7 +73,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.LinkedList; -import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** System implementation of {@link IVibratorService}. */ @@ -120,7 +119,6 @@ public class VibratorService extends IVibratorService.Stub { private int mCurVibUid = -1; private ExternalVibrationHolder mCurrentExternalVibration; private boolean mLowPowerMode; - private SparseArray mAlwaysOnEffects = new SparseArray<>(); private final IUidObserver mUidObserver = new IUidObserver.Stub() { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, @@ -379,51 +377,6 @@ public class VibratorService extends IVibratorService.Stub { return mVibratorController.arePrimitivesSupported(primitiveIds); } - private static List asList(int... vals) { - if (vals == null) { - return null; - } - List l = new ArrayList<>(vals.length); - for (int val : vals) { - l.add(val); - } - return l; - } - - @Override // Binder call - public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, - VibrationAttributes attrs) { - if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) { - throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission"); - } - if (!mVibratorController.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { - Slog.e(TAG, "Always-on effects not supported."); - return false; - } - if (effect == null) { - synchronized (mLock) { - mAlwaysOnEffects.delete(alwaysOnId); - mVibratorController.updateAlwaysOn(alwaysOnId, /* effect= */ null); - } - } else { - if (!verifyVibrationEffect(effect)) { - return false; - } - if (!(effect instanceof VibrationEffect.Prebaked)) { - Slog.e(TAG, "Only prebaked effects supported for always-on."); - return false; - } - attrs = fixupVibrationAttributes(attrs); - synchronized (mLock) { - Vibration vib = new Vibration(null, mNextVibrationId.getAndIncrement(), effect, - attrs, uid, opPkg, null); - mAlwaysOnEffects.put(alwaysOnId, vib); - updateAlwaysOnLocked(alwaysOnId, vib); - } - } - return true; - } - private void verifyIncomingUid(int uid) { if (uid == Binder.getCallingUid()) { return; @@ -769,7 +722,7 @@ public class VibratorService extends IVibratorService.Stub { // We might be getting calls from within system_server, so we don't actually // want to throw a SecurityException here. Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid); - endVibrationLocked(vib, Vibration.Status.ERROR_APP_OPS); + endVibrationLocked(vib, Vibration.Status.IGNORED_ERROR_APP_OPS); } else { endVibrationLocked(vib, Vibration.Status.IGNORED_APP_OPS); } @@ -829,8 +782,6 @@ public class VibratorService extends IVibratorService.Stub { // If the state changes out from under us then just reset. doCancelVibrateLocked(Vibration.Status.CANCELLED); } - - updateAlwaysOnLocked(); } } @@ -844,24 +795,6 @@ public class VibratorService extends IVibratorService.Stub { return false; } - private void updateAlwaysOnLocked(int id, Vibration vib) { - VibrationEffect.Prebaked effect; - if (!shouldVibrate(vib)) { - effect = null; - } else { - effect = mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()); - } - mVibratorController.updateAlwaysOn(id, effect); - } - - private void updateAlwaysOnLocked() { - for (int i = 0; i < mAlwaysOnEffects.size(); i++) { - int id = mAlwaysOnEffects.keyAt(i); - Vibration vib = mAlwaysOnEffects.valueAt(i); - updateAlwaysOnLocked(id, vib); - } - } - private void doVibratorOn(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn"); try { @@ -1289,7 +1222,7 @@ public class VibratorService extends IVibratorService.Stub { static class Injector { VibratorController createVibratorController(OnVibrationCompleteListener listener) { - return new VibratorController(/* vibratorId= */ 0, listener); + return new VibratorController(/* vibratorId= */ -1, listener); } Handler createHandler(Looper looper) { @@ -1373,7 +1306,7 @@ public class VibratorService extends IVibratorService.Stub { vibHolder.scale = SCALE_MUTE; if (mode == AppOpsManager.MODE_ERRORED) { Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid()); - endVibrationLocked(vibHolder, Vibration.Status.ERROR_APP_OPS); + endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS); } else { endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS); } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index e2cdd02deab91..b0266d025c08d 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -47,7 +47,7 @@ public class Vibration { FINISHED, FORWARDED_TO_INPUT_DEVICES, CANCELLED, - ERROR_APP_OPS, + IGNORED_ERROR_APP_OPS, IGNORED, IGNORED_APP_OPS, IGNORED_BACKGROUND, diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_VibratorManagerService.cpp index dae9cefdd1f40..71de9bda3c4f1 100644 --- a/services/core/jni/com_android_server_VibratorManagerService.cpp +++ b/services/core/jni/com_android_server_VibratorManagerService.cpp @@ -26,8 +26,13 @@ #include +#include "com_android_server_VibratorManagerService.h" + namespace android { +static std::mutex gManagerMutex; +static vibrator::ManagerHalWrapper* gManager GUARDED_BY(gManagerMutex) = nullptr; + class NativeVibratorManagerService { public: NativeVibratorManagerService() : mHal(std::make_unique()) {} @@ -39,9 +44,16 @@ private: const std::unique_ptr mHal; }; +vibrator::ManagerHalWrapper* android_server_VibratorManagerService_getManager() { + std::lock_guard lock(gManagerMutex); + return gManager; +} + static void destroyNativeService(void* ptr) { NativeVibratorManagerService* service = reinterpret_cast(ptr); if (service) { + std::lock_guard lock(gManagerMutex); + gManager = nullptr; delete service; } } @@ -49,6 +61,10 @@ static void destroyNativeService(void* ptr) { static jlong nativeInit(JNIEnv* /* env */, jclass /* clazz */) { std::unique_ptr service = std::make_unique(); + { + std::lock_guard lock(gManagerMutex); + gManager = service->hal(); + } return reinterpret_cast(service.release()); } diff --git a/services/core/jni/com_android_server_VibratorManagerService.h b/services/core/jni/com_android_server_VibratorManagerService.h new file mode 100644 index 0000000000000..3f2a322b19ee6 --- /dev/null +++ b/services/core/jni/com_android_server_VibratorManagerService.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_SERVER_VIBRATOR_MANAGER_SERVICE_H +#define _ANDROID_SERVER_VIBRATOR_MANAGER_SERVICE_H + +#include + +namespace android { + +extern vibrator::ManagerHalWrapper* android_server_VibratorManagerService_getManager(); + +} // namespace android + +#endif // _ANDROID_SERVER_VIBRATOR_MANAGER_SERVICE_H diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index afce5379febbe..7ed37481f8cb5 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -29,6 +29,8 @@ #include +#include "com_android_server_VibratorManagerService.h" + namespace V1_0 = android::hardware::vibrator::V1_0; namespace V1_1 = android::hardware::vibrator::V1_1; namespace V1_2 = android::hardware::vibrator::V1_2; @@ -70,13 +72,26 @@ static_assert(static_cast(V1_3::Effect::RINGTONE_15) == static_assert(static_cast(V1_3::Effect::TEXTURE_TICK) == static_cast(aidl::Effect::TEXTURE_TICK)); +static std::shared_ptr findVibrator(int32_t vibratorId) { + // TODO(b/167946816): remove this once VibratorService is removed. + if (vibratorId < 0) { + return std::move(std::make_unique()); + } + vibrator::ManagerHalWrapper* manager = android_server_VibratorManagerService_getManager(); + if (manager == nullptr) { + return nullptr; + } + auto result = manager->getVibrator(vibratorId); + return result.isOk() ? std::move(result.value()) : nullptr; +} + class VibratorControllerWrapper { public: VibratorControllerWrapper(JNIEnv* env, int32_t vibratorId, jobject callbackListener) - // TODO(b/167946816): use ManagerHalController to get vibrator by id - : mHal(std::make_unique()), + : mHal(std::move(findVibrator(vibratorId))), mVibratorId(vibratorId), mCallbackListener(env->NewGlobalRef(callbackListener)) { + LOG_ALWAYS_FATAL_IF(mHal == nullptr, "Unable to find reference to vibrator hal"); LOG_ALWAYS_FATAL_IF(mCallbackListener == nullptr, "Unable to create global reference to vibration callback handler"); } @@ -97,7 +112,7 @@ public: } private: - const std::unique_ptr mHal; + const std::shared_ptr mHal; const int32_t mVibratorId; const jobject mCallbackListener; }; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 6525e1126478a..92c64f822b782 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1252,6 +1252,10 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(DropBoxManagerService.class); t.traceEnd(); + t.traceBegin("StartVibratorManagerService"); + mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class); + t.traceEnd(); + t.traceBegin("StartVibratorService"); vibrator = new VibratorService(context); ServiceManager.addService("vibrator", vibrator); diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java index 044bdbadb946b..9c58daf412f8c 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java @@ -19,17 +19,33 @@ package com.android.server; import static com.android.server.testutils.TestUtils.assertExpectException; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +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.os.CombinedVibrationEffect; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; import android.os.Process; import android.os.VibrationAttributes; import android.os.VibrationEffect; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; +import com.android.server.vibrator.VibratorController; + +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -37,6 +53,9 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.HashMap; +import java.util.Map; + /** * Tests for {@link VibratorManagerService}. * @@ -51,24 +70,54 @@ public class VibratorManagerServiceTest { private static final VibrationAttributes ALARM_ATTRS = new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build(); - @Rule - public MockitoRule rule = MockitoJUnit.rule(); + @Rule public MockitoRule rule = MockitoJUnit.rule(); - @Mock - private VibratorManagerService.NativeWrapper mNativeWrapperMock; + @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock; + @Mock private PowerManagerInternal mPowerManagerInternalMock; + @Mock private PowerSaveState mPowerSaveStateMock; + + private final Map mNativeWrappers = new HashMap<>(); + + private TestLooper mTestLooper; @Before public void setUp() throws Exception { + mTestLooper = new TestLooper(); + + when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION)) + .thenReturn(mPowerSaveStateMock); + + addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock); + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(PowerManagerInternal.class); } private VibratorManagerService createService() { - return new VibratorManagerService(InstrumentationRegistry.getContext(), + VibratorManagerService service = new VibratorManagerService( + InstrumentationRegistry.getContext(), new VibratorManagerService.Injector() { @Override VibratorManagerService.NativeWrapper getNativeWrapper() { return mNativeWrapperMock; } + + @Override + Handler createHandler(Looper looper) { + return new Handler(mTestLooper.getLooper()); + } + + @Override + VibratorController createVibratorController(int vibratorId, + VibratorController.OnVibrationCompleteListener listener) { + return new VibratorController( + vibratorId, listener, mNativeWrappers.get(vibratorId)); + } }); + service.systemReady(); + return service; } @Test @@ -85,8 +134,125 @@ public class VibratorManagerServiceTest { @Test public void getVibratorIds_withNonEmptyResultFromNative_returnsSameArray() { - when(mNativeWrapperMock.getVibratorIds()).thenReturn(new int[]{1, 2}); - assertArrayEquals(new int[]{1, 2}, createService().getVibratorIds()); + mNativeWrappers.put(1, mockVibrator(0)); + mNativeWrappers.put(2, mockVibrator(0)); + when(mNativeWrapperMock.getVibratorIds()).thenReturn(new int[]{2, 1}); + assertArrayEquals(new int[]{2, 1}, createService().getVibratorIds()); + } + + @Test + public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() { + VibratorController.NativeWrapper[] vibratorMocks = new VibratorController.NativeWrapper[] { + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL), + mockVibrator(0), + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL), + }; + mockVibrators(vibratorMocks); + + CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); + assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + + // Only vibrators 0 and 2 have always-on capabilities. + verify(vibratorMocks[0]).alwaysOnEnable( + eq(1L), eq((long) VibrationEffect.EFFECT_CLICK), + eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG)); + verify(vibratorMocks[1], never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); + verify(vibratorMocks[2]).alwaysOnEnable( + eq(1L), eq((long) VibrationEffect.EFFECT_CLICK), + eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void setAlwaysOnEffect_withStereo_enablesAlwaysOnEffectToAllVibratorsWithCapability() { + VibratorController.NativeWrapper[] vibratorMocks = new VibratorController.NativeWrapper[] { + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL), + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL), + mockVibrator(0), + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL), + }; + mockVibrators(vibratorMocks); + + CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() + .addVibrator(0, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)) + .addVibrator(1, VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)) + .addVibrator(2, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)) + .combine(); + assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + + // Enables click on vibrator 0 and tick on vibrator 1 only. + verify(vibratorMocks[0]).alwaysOnEnable( + eq(1L), eq((long) VibrationEffect.EFFECT_CLICK), + eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG)); + verify(vibratorMocks[1]).alwaysOnEnable( + eq(1L), eq((long) VibrationEffect.EFFECT_TICK), + eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG)); + verify(vibratorMocks[2], never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); + verify(vibratorMocks[3], never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); + } + + @Test + public void setAlwaysOnEffect_withNullEffect_disablesAlwaysOnEffects() { + VibratorController.NativeWrapper[] vibratorMocks = new VibratorController.NativeWrapper[] { + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL), + mockVibrator(0), + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL), + }; + mockVibrators(vibratorMocks); + + assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, null, ALARM_ATTRS)); + + // Disables only 0 and 2 that have capability. + verify(vibratorMocks[0]).alwaysOnDisable(eq(1L)); + verify(vibratorMocks[1], never()).alwaysOnDisable(anyLong()); + verify(vibratorMocks[2]).alwaysOnDisable(eq(1L)); + } + + @Test + public void setAlwaysOnEffect_withNonPrebakedEffect_ignoresEffect() { + VibratorController.NativeWrapper vibratorMock = + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL); + mockVibrators(vibratorMock); + + CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( + VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); + assertFalse(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + + verify(vibratorMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); + verify(vibratorMock, never()).alwaysOnDisable(anyLong()); + } + + @Test + public void setAlwaysOnEffect_withNonSyncedEffect_ignoresEffect() { + VibratorController.NativeWrapper vibratorMock = + mockVibrator(IVibrator.CAP_ALWAYS_ON_CONTROL); + mockVibrators(vibratorMock); + + CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential() + .addNext(0, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .combine(); + assertFalse(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); + + verify(vibratorMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); + verify(vibratorMock, never()).alwaysOnDisable(anyLong()); + } + + @Test + public void setAlwaysOnEffect_withNoVibratorWithCapability_ignoresEffect() { + VibratorController.NativeWrapper vibratorMock = mockVibrator(0); + mockVibrators(vibratorMock); + VibratorManagerService service = createService(); + + CombinedVibrationEffect mono = CombinedVibrationEffect.createSynced( + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); + CombinedVibrationEffect stereo = CombinedVibrationEffect.startSynced() + .addVibrator(0, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)) + .combine(); + assertFalse(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 1, mono, ALARM_ATTRS)); + assertFalse(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 2, stereo, ALARM_ATTRS)); + + verify(vibratorMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); + verify(vibratorMock, never()).alwaysOnDisable(anyLong()); } @Test @@ -107,4 +273,24 @@ public class VibratorManagerServiceTest { assertExpectException(UnsupportedOperationException.class, "Not implemented", () -> service.cancelVibrate(service)); } + + private VibratorController.NativeWrapper mockVibrator(int capabilities) { + VibratorController.NativeWrapper wrapper = mock(VibratorController.NativeWrapper.class); + when(wrapper.getCapabilities()).thenReturn((long) capabilities); + return wrapper; + } + + private void mockVibrators(VibratorController.NativeWrapper... wrappers) { + int[] ids = new int[wrappers.length]; + for (int i = 0; i < wrappers.length; i++) { + ids[i] = i; + mNativeWrappers.put(i, wrappers[i]); + } + when(mNativeWrapperMock.getVibratorIds()).thenReturn(ids); + } + + private static void addLocalServiceMock(Class clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } } diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java index 64f31358ccb33..83ba621de48d4 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java @@ -287,43 +287,6 @@ public class VibratorServiceTest { })); } - @Test - public void setAlwaysOnEffect_withCapabilityAndValidEffect_enablesAlwaysOnEffect() { - mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL); - - assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, - VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS)); - verify(mNativeWrapperMock).alwaysOnEnable( - eq(1L), eq((long) VibrationEffect.EFFECT_CLICK), - eq((long) VibrationEffect.EFFECT_STRENGTH_STRONG)); - } - - @Test - public void setAlwaysOnEffect_withNonPrebakedEffect_ignoresEffect() { - mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL); - - assertFalse(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, - VibrationEffect.createOneShot(100, 255), ALARM_ATTRS)); - verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong()); - verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); - } - - @Test - public void setAlwaysOnEffect_withNullEffect_disablesAlwaysOnEffect() { - mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL); - - assertTrue(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, null, ALARM_ATTRS)); - verify(mNativeWrapperMock).alwaysOnDisable(eq(1L)); - } - - @Test - public void setAlwaysOnEffect_withoutCapability_ignoresEffect() { - assertFalse(createService().setAlwaysOnEffect(UID, PACKAGE_NAME, 1, - VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS)); - verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong()); - verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); - } - @Test public void vibrate_withRingtone_usesRingtoneSettings() { setRingerMode(AudioManager.RINGER_MODE_NORMAL);