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

Commit 2b10f2aa authored by Adrian Roos's avatar Adrian Roos
Browse files

VibratorManagerService: Stop ongoing vibration when phone enters DND mode

Fixes: 230745615
Test: atest 'VibratorManagerServiceTest#vibrate_thenDeniedAppOps_getsCancelled'
Flag: android.os.vibrator.cancel_by_appops
Change-Id: I791b92d6fe1aecb419c48ada0f3e10e9c3870ad6
parent 99d67d33
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -31,3 +31,14 @@ flag {
    description: "Enables the adaptive haptics feature"
    bug: "305961689"
}

flag {
    namespace: "haptics"
    name: "cancel_by_appops"
    description: "Cancels ongoing vibrations when the appops mode changes to disallow them"
    bug: "230745615"
    is_fixed_read_only: true
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -146,6 +146,7 @@ message VibrationProto {
        IGNORED_FROM_VIRTUAL_DEVICE = 26;
        IGNORED_ON_WIRELESS_CHARGER = 27;
        IGNORED_MISSING_PERMISSION = 28;
        CANCELLED_BY_APP_OPS = 29;
        reserved 17; // prev IGNORED_UNKNOWN_VIBRATION
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ abstract class Vibration {
        CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
        CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
        CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
        CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
        IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
        IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
        IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
+33 −0
Original line number Diff line number Diff line
@@ -193,6 +193,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        }
    };

    @VisibleForTesting
    final AppOpsManager.OnOpChangedInternalListener mAppOpsChangeListener =
            new AppOpsManager.OnOpChangedInternalListener() {
                @Override
                public void onOpChanged(int op, String packageName) {
                    if (op != AppOpsManager.OP_VIBRATE) {
                        return;
                    }
                    synchronized (mLock) {
                        if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
                            clearNextVibrationLocked(
                                    new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_APP_OPS));
                        }
                        if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
                            mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
                                    Vibration.Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
                        }
                    }
                }
            };

    static native long nativeInit(OnSyncedVibrationCompleteListener listener);

    static native long nativeGetFinalizer();
@@ -238,6 +259,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        mBatteryStatsService = injector.getBatteryStatsService();

        mAppOps = mContext.getSystemService(AppOpsManager.class);
        if (Flags.cancelByAppops()) {
            mAppOps.startWatchingMode(AppOpsManager.OP_VIBRATE, null, mAppOpsChangeListener);
        }

        PowerManager pm = context.getSystemService(PowerManager.class);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
@@ -1389,6 +1413,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                vib.stats.getCreateUptimeMillis());
    }

    @GuardedBy("mLock")
    private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) {
        if (conductor == null) {
            return false;
        }
        return checkAppOpModeLocked(conductor.getVibration().callerInfo)
                != AppOpsManager.MODE_ALLOWED;
    }

    @GuardedBy("mLock")
    private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
        for (int i = 0; i < mVibrators.size(); i++) {
+28 −0
Original line number Diff line number Diff line
@@ -780,6 +780,34 @@ public class VibratorManagerServiceTest {
                eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
    }

    @Test
    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS)
    public void vibrate_thenDeniedAppOps_getsCancelled() throws Throwable {
        mockVibrators(1);
        VibratorManagerService service = createSystemReadyService();

        var vib = vibrate(service,
                VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), RINGTONE_ATTRS);

        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));

        when(mAppOpsManagerMock.checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
                eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString()))
                .thenReturn(AppOpsManager.MODE_IGNORED);

        service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null);

        assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));

        var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
                .writeVibrationReportedAsync(statsInfoCaptor.capture());

        VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0);
        assertEquals(Vibration.Status.CANCELLED_BY_APP_OPS.getProtoEnumValue(),
                touchMetrics.status);
    }

    @Test
    public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
        VibratorManagerService service = createSystemReadyService();