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

Commit 6a57fba3 authored by fayey's avatar fayey
Browse files

Ambient Activation p2.2

This change enables the OP_RECEIVE_SANDBOX_TRIGGER_AUDIO turning on the Microphone privacy indicator.

Bug: 287264308
Test: atest com.android.systemui.appops.AppOpsControllerTest
Test: atest com.android.systemui.privacy.AppOpsPrivacyItemMonitorTest
Test-manual:
When voice activation op is not enabled (AppOpsPolicy.IS_VOICE_ACTIVATION_OP_ENABLED = false && AbstractDetector.IS_IDENTITY_WITH_ATTRIBUTION_TAG = false), no OP_RECEIVE_SANDBOX_TRIGGER_AUDIO is noted. https://paste.googleplex.com/5513928401485824

When voice activation op is enabled (AppOpsPolicy.IS_VOICE_ACTIVATION_OP_ENABLED = true && AbstractDetector.IS_IDENTITY_WITH_ATTRIBUTION_TAG = true), and test apk with attribution tag is installed: https://paste.googleplex.com/6105488876896256

when mic access is blocked by toggle in the tile, no privacy indicator is turned on, no voice activation and hence no OP_RECEIVE_SANDBOX_TRIGGER_AUDIO is noted.

Change-Id: I5ae494a8d319d7f8b81e8501abf9313aaae58fba
parent 84f5fa0b
Loading
Loading
Loading
Loading
+57 −11
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;

import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -33,6 +34,7 @@ import android.os.UserHandle;
import android.permission.PermissionManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;

import androidx.annotation.WorkerThread;
@@ -96,19 +98,58 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
    private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid =
            new SparseArray<>();

    protected static final int[] OPS = new int[] {
            AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
            AppOpsManager.OP_CAMERA,
            AppOpsManager.OP_PHONE_CALL_CAMERA,
            AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
    @VisibleForTesting
    protected static final int[] OPS_MIC = new int[] {
            AppOpsManager.OP_RECORD_AUDIO,
            AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
            AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
            AppOpsManager.OP_PHONE_CALL_MICROPHONE,
            AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
            AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
    };

    protected static final int[] OPS_CAMERA = new int[] {
            AppOpsManager.OP_CAMERA,
            AppOpsManager.OP_PHONE_CALL_CAMERA
    };

    protected static final int[] OPS_LOC = new int[] {
            AppOpsManager.OP_FINE_LOCATION,
            AppOpsManager.OP_COARSE_LOCATION,
            AppOpsManager.OP_FINE_LOCATION
            AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION
    };

    protected static final int[] OPS_OTHERS = new int[] {
            AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
            AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO
    };


   protected static final int[] OPS = concatOps(OPS_MIC, OPS_CAMERA, OPS_LOC, OPS_OTHERS);

    /**
     * @param opArrays the given op arrays.
     * @return the concatenations of the given op arrays. Null arrays are treated as empty.
     */
    private static int[] concatOps(@Nullable int[]...opArrays) {
        if (opArrays == null) {
            return new int[0];
        }
        int totalLength = 0;
        for (int[] opArray : opArrays) {
            if (opArray == null || opArray.length == 0) {
                continue;
            }
            totalLength += opArray.length;
        }
        final int[] concatOps = new int[totalLength];
        int index = 0;
        for (int[] opArray : opArrays) {
            if (opArray == null || opArray.length == 0) continue;
            System.arraycopy(opArray, 0, concatOps, index, opArray.length);
            index += opArray.length;
        }
        return concatOps;
    }

    @Inject
    public AppOpsControllerImpl(
            Context context,
@@ -533,12 +574,17 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
    }

    private boolean isOpCamera(int op) {
        return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA;
        for (int i = 0; i < OPS_CAMERA.length; i++) {
            if (op == OPS_CAMERA[i]) return true;
        }
        return false;
    }

    private boolean isOpMicrophone(int op) {
        return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE
                || op == AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
        for (int i = 0; i < OPS_MIC.length; i++) {
            if (op == OPS_MIC[i]) return true;
        }
        return false;
    }

    protected class H extends Handler {
+7 −3
Original line number Diff line number Diff line
@@ -53,11 +53,14 @@ class AppOpsPrivacyItemMonitor @Inject constructor(

    @VisibleForTesting
    companion object {
        val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA,
                AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO,
        val OPS_MIC_CAMERA = intArrayOf(
                AppOpsManager.OP_CAMERA,
                AppOpsManager.OP_PHONE_CALL_CAMERA,
                AppOpsManager.OP_RECORD_AUDIO,
                AppOpsManager.OP_PHONE_CALL_MICROPHONE,
                AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO)
                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
                AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO)
        val OPS_LOCATION = intArrayOf(
                AppOpsManager.OP_COARSE_LOCATION,
                AppOpsManager.OP_FINE_LOCATION)
@@ -212,6 +215,7 @@ class AppOpsPrivacyItemMonitor @Inject constructor(
            AppOpsManager.OP_PHONE_CALL_MICROPHONE,
            AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
            AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
            AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
            AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
            else -> return null
        }
+66 −31
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.appops;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;

import static com.android.systemui.appops.AppOpsControllerImpl.OPS_MIC;

import static junit.framework.TestCase.assertFalse;

import static org.junit.Assert.assertEquals;
@@ -171,6 +173,28 @@ public class AppOpsControllerTest extends SysuiTestCase {
                TEST_UID, TEST_PACKAGE_NAME, true);
    }


    // Only the app ops in the {@link com.android.systemui.appops.AppOpsControllerImpl.OPS} will be
    // supported by the {@link AppOpsControllerImpl} to add callbacks. The state changes of active
    // app ops will be notified by the callback.
    @Test
    public void addCallback_partialIncludedCode() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
                AppOpsManager.OP_FINE_LOCATION}, mCallback);
        mController.onOpActiveChanged(
                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
        mController.onOpActiveChanged(
                AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID, TEST_PACKAGE_NAME,
                true);
        assertEquals(2, mController.getActiveAppOps().size());

        mTestableLooper.processAllMessages();
        verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
                TEST_UID, TEST_PACKAGE_NAME, true);
        verify(mCallback, never()).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
                TEST_UID, TEST_PACKAGE_NAME, true);
    }

    @Test
    public void addCallback_notIncludedCode() {
        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
@@ -504,41 +528,17 @@ public class AppOpsControllerTest extends SysuiTestCase {
    }

    @Test
    public void testUnpausedRecordingSentActive() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
        mTestableLooper.processAllMessages();
        mController.onOpActiveChanged(
                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);

        mTestableLooper.processAllMessages();
        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());

        mTestableLooper.processAllMessages();

        verify(mCallback).onActiveStateChanged(
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
    public void testUnPausedRecordingSentActive() {
        for (int i = 0; i < OPS_MIC.length; i++) {
            verifyUnPausedSentActive(OPS_MIC[i]);
        }
    }

    @Test
    public void testAudioPausedSentInactive() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
        mTestableLooper.processAllMessages();
        mController.onOpActiveChanged(
                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();

        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
        when(mockARC.isClientSilenced()).thenReturn(true);

        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
        mTestableLooper.processAllMessages();

        InOrder inOrder = inOrder(mCallback);
        inOrder.verify(mCallback).onActiveStateChanged(
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
        inOrder.verify(mCallback).onActiveStateChanged(
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
        for (int i = 0; i < OPS_MIC.length; i++) {
            verifyAudioPausedSentInactive(OPS_MIC[i]);
        }
    }

    @Test
@@ -673,6 +673,41 @@ public class AppOpsControllerTest extends SysuiTestCase {
        assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
    }

    private void verifyUnPausedSentActive(int micOpCode) {
        mController.addCallback(new int[]{micOpCode}, mCallback);
        mTestableLooper.processAllMessages();
        mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID,
                TEST_PACKAGE_NAME, true);

        mTestableLooper.processAllMessages();
        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());

        mTestableLooper.processAllMessages();

        verify(mCallback).onActiveStateChanged(micOpCode, TEST_UID, TEST_PACKAGE_NAME, true);
    }

    private void verifyAudioPausedSentInactive(int micOpCode) {
        mController.addCallback(new int[]{micOpCode}, mCallback);
        mTestableLooper.processAllMessages();
        mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID_OTHER,
                TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();

        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
        when(mockARC.isClientSilenced()).thenReturn(true);

        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
        mTestableLooper.processAllMessages();

        InOrder inOrder = inOrder(mCallback);
        inOrder.verify(mCallback).onActiveStateChanged(
                micOpCode, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
        inOrder.verify(mCallback).onActiveStateChanged(
                micOpCode, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
    }

    private class TestHandler extends AppOpsControllerImpl.H {
        TestHandler(Looper looper) {
            mController.super(looper);
+10 −0
Original line number Diff line number Diff line
@@ -142,6 +142,16 @@ class AppOpsPrivacyItemMonitorTest : SysuiTestCase() {
        assertEquals(1, appOpsPrivacyItemMonitor.getActivePrivacyItems().size)
    }

    @Test
    fun testVoiceActivationPrivacyItems() {
        doReturn(listOf(AppOpItem(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID,
                TEST_PACKAGE_NAME, 0)))
                .`when`(appOpsController).getActiveAppOps(anyBoolean())
        val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
        assertEquals(1, privacyItems.size)
        assertEquals(PrivacyType.TYPE_MICROPHONE, privacyItems[0].privacyType)
    }

    @Test
    fun testSimilarItemsDifferentTimeStamp() {
        doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),