Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e755fb3 authored by Lais Andrade's avatar Lais Andrade
Browse files

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
parent c9991d0a
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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);
+0 −2
Original line number Diff line number Diff line
@@ -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);
+8 −3
Original line number Diff line number Diff line
@@ -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) {
+409 −8
Original line number Diff line number Diff line
@@ -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();

@@ -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<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");
    }
@@ -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 {
@@ -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. */
@@ -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;
+3 −70

File changed.

Preview size limit exceeded, changes collapsed.

Loading