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