Loading core/java/android/os/IVibratorManagerService.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -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); Loading core/java/android/os/IVibratorService.aidl +0 −2 Original line number Diff line number Diff line Loading @@ -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); Loading core/java/android/os/SystemVibrator.java +8 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading Loading @@ -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); } Loading Loading @@ -255,7 +261,6 @@ public class SystemVibrator extends Vibrator { } } @Override public void cancel() { if (mService == null) { Loading services/core/java/com/android/server/VibratorManagerService.java +409 −8 Original line number Diff line number Diff line Loading @@ -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<VibratorController> mVibrators; @GuardedBy("mLock") private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>(); private VibrationSettings mVibrationSettings; private VibrationScaler mVibrationScaler; @GuardedBy("mLock") private boolean mLowPowerMode; static native long nativeInit(); Loading @@ -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 Loading @@ -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<VibrationEffect.Prebaked> 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"); } Loading @@ -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<VibrationEffect.Prebaked> fixupAlwaysOnEffectsLocked( CombinedVibrationEffect effect) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked"); try { SparseArray<VibrationEffect> 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<VibrationEffect.Prebaked> 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<VibratorController> consumer) { for (int i = 0; i < mVibrators.size(); i++) { consumer.accept(mVibrators.valueAt(i)); } } @GuardedBy("mLock") private <T> SparseArray<T> transformAllVibratorsLocked(Function<VibratorController, T> fn) { SparseArray<T> 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 { Loading @@ -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<VibratorManagerService> 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<VibrationEffect.Prebaked> effects; AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs, SparseArray<VibrationEffect.Prebaked> 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. */ Loading Loading @@ -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; Loading services/core/java/com/android/server/VibratorService.java +3 −70 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/os/IVibratorManagerService.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
core/java/android/os/IVibratorService.aidl +0 −2 Original line number Diff line number Diff line Loading @@ -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); Loading
core/java/android/os/SystemVibrator.java +8 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 Loading Loading @@ -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); } Loading Loading @@ -255,7 +261,6 @@ public class SystemVibrator extends Vibrator { } } @Override public void cancel() { if (mService == null) { Loading
services/core/java/com/android/server/VibratorManagerService.java +409 −8 Original line number Diff line number Diff line Loading @@ -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<VibratorController> mVibrators; @GuardedBy("mLock") private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>(); private VibrationSettings mVibrationSettings; private VibrationScaler mVibrationScaler; @GuardedBy("mLock") private boolean mLowPowerMode; static native long nativeInit(); Loading @@ -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 Loading @@ -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<VibrationEffect.Prebaked> 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"); } Loading @@ -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<VibrationEffect.Prebaked> fixupAlwaysOnEffectsLocked( CombinedVibrationEffect effect) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked"); try { SparseArray<VibrationEffect> 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<VibrationEffect.Prebaked> 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<VibratorController> consumer) { for (int i = 0; i < mVibrators.size(); i++) { consumer.accept(mVibrators.valueAt(i)); } } @GuardedBy("mLock") private <T> SparseArray<T> transformAllVibratorsLocked(Function<VibratorController, T> fn) { SparseArray<T> 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 { Loading @@ -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<VibratorManagerService> 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<VibrationEffect.Prebaked> effects; AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs, SparseArray<VibrationEffect.Prebaked> 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. */ Loading Loading @@ -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; Loading
services/core/java/com/android/server/VibratorService.java +3 −70 File changed.Preview size limit exceeded, changes collapsed. Show changes