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

Commit 754264e6 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Keep track of audio recordings that are silenced"

parents ad95d5b3 44fc7138
Loading
Loading
Loading
Loading
+14 −5
Original line number Diff line number Diff line
@@ -25,7 +25,9 @@ public class AppOpItem {
    private int mUid;
    private String mPackageName;
    private long mTimeStarted;
    private String mState;
    private StringBuilder mState;
    // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
    private boolean mSilenced;

    public AppOpItem(int code, int uid, String packageName, long timeStarted) {
        this.mCode = code;
@@ -36,9 +38,8 @@ public class AppOpItem {
                .append("AppOpItem(")
                .append("Op code=").append(code).append(", ")
                .append("UID=").append(uid).append(", ")
                .append("Package name=").append(packageName)
                .append(")")
                .toString();
                .append("Package name=").append(packageName).append(", ")
                .append("Paused=");
    }

    public int getCode() {
@@ -57,8 +58,16 @@ public class AppOpItem {
        return mTimeStarted;
    }

    public void setSilenced(boolean silenced) {
        mSilenced = silenced;
    }

    public boolean isSilenced() {
        return mSilenced;
    }

    @Override
    public String toString() {
        return mState;
        return mState.append(mSilenced).append(")").toString();
    }
}
+84 −7
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.appops;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.AudioRecordingConfiguration;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -63,6 +65,7 @@ public class AppOpsControllerImpl implements AppOpsController,
    private static final boolean DEBUG = false;

    private final AppOpsManager mAppOps;
    private final AudioManager mAudioManager;
    private H mBGHandler;
    private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
    private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>();
@@ -73,6 +76,9 @@ public class AppOpsControllerImpl implements AppOpsController,
    private final List<AppOpItem> mActiveItems = new ArrayList<>();
    @GuardedBy("mNotedItems")
    private final List<AppOpItem> mNotedItems = new ArrayList<>();
    @GuardedBy("mActiveItems")
    private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid =
            new SparseArray<>();

    protected static final int[] OPS = new int[] {
            AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
@@ -88,7 +94,8 @@ public class AppOpsControllerImpl implements AppOpsController,
            Context context,
            @Background Looper bgLooper,
            DumpManager dumpManager,
            PermissionFlagsCache cache
            PermissionFlagsCache cache,
            AudioManager audioManager
    ) {
        mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        mFlagsCache = cache;
@@ -97,6 +104,7 @@ public class AppOpsControllerImpl implements AppOpsController,
        for (int i = 0; i < numOps; i++) {
            mCallbacksByCode.put(OPS[i], new ArraySet<>());
        }
        mAudioManager = audioManager;
        dumpManager.registerDumpable(TAG, this);
    }

@@ -111,12 +119,19 @@ public class AppOpsControllerImpl implements AppOpsController,
        if (listening) {
            mAppOps.startWatchingActive(OPS, this);
            mAppOps.startWatchingNoted(OPS, this);
            mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
            mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
                    mAudioManager.getActiveRecordingConfigurations()));

        } else {
            mAppOps.stopWatchingActive(this);
            mAppOps.stopWatchingNoted(this);
            mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);

            mBGHandler.removeCallbacksAndMessages(null); // null removes all
            synchronized (mActiveItems) {
                mActiveItems.clear();
                mRecordingsByUid.clear();
            }
            synchronized (mNotedItems) {
                mNotedItems.clear();
@@ -189,9 +204,12 @@ public class AppOpsControllerImpl implements AppOpsController,
            AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
            if (item == null && active) {
                item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
                if (code == AppOpsManager.OP_RECORD_AUDIO) {
                    item.setSilenced(isAnyRecordingPausedLocked(uid));
                }
                mActiveItems.add(item);
                if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
                return true;
                return !item.isSilenced();
            } else if (item != null && !active) {
                mActiveItems.remove(item);
                if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -215,7 +233,7 @@ public class AppOpsControllerImpl implements AppOpsController,
            active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
        }
        if (!active) {
            notifySuscribers(code, uid, packageName, false);
            notifySuscribersWorker(code, uid, packageName, false);
        }
    }

@@ -324,7 +342,7 @@ public class AppOpsControllerImpl implements AppOpsController,
                AppOpItem item = mActiveItems.get(i);
                if ((userId == UserHandle.USER_ALL
                        || UserHandle.getUserId(item.getUid()) == userId)
                        && isUserVisible(item)) {
                        && isUserVisible(item) && !item.isSilenced()) {
                    list.add(item);
                }
            }
@@ -343,6 +361,10 @@ public class AppOpsControllerImpl implements AppOpsController,
        return list;
    }

    private void notifySuscribers(int code, int uid, String packageName, boolean active) {
        mBGHandler.post(() -> notifySuscribersWorker(code, uid, packageName, active));
    }

    @Override
    public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
        if (DEBUG) {
@@ -360,7 +382,7 @@ public class AppOpsControllerImpl implements AppOpsController,
        // If active is false, we only send the update if the op is not actively noted (prevent
        // early removal)
        if (!alsoNoted) {
            mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
            notifySuscribers(code, uid, packageName, active);
        }
    }

@@ -378,11 +400,11 @@ public class AppOpsControllerImpl implements AppOpsController,
            alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
        }
        if (!alsoActive) {
            mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
            notifySuscribers(code, uid, packageName, true);
        }
    }

    private void notifySuscribers(int code, int uid, String packageName, boolean active) {
    private void notifySuscribersWorker(int code, int uid, String packageName, boolean active) {
        if (mCallbacksByCode.contains(code) && isUserVisible(code, uid, packageName)) {
            if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
            for (Callback cb: mCallbacksByCode.get(code)) {
@@ -408,6 +430,61 @@ public class AppOpsControllerImpl implements AppOpsController,

    }

    private boolean isAnyRecordingPausedLocked(int uid) {
        List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid);
        if (configs == null) return false;
        int configsNum = configs.size();
        for (int i = 0; i < configsNum; i++) {
            AudioRecordingConfiguration config = configs.get(i);
            if (config.isClientSilenced()) return true;
        }
        return false;
    }

    private void updateRecordingPausedStatus() {
        synchronized (mActiveItems) {
            int size = mActiveItems.size();
            for (int i = 0; i < size; i++) {
                AppOpItem item = mActiveItems.get(i);
                if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
                    boolean paused = isAnyRecordingPausedLocked(item.getUid());
                    if (item.isSilenced() != paused) {
                        item.setSilenced(paused);
                        notifySuscribers(
                                item.getCode(),
                                item.getUid(),
                                item.getPackageName(),
                                !item.isSilenced()
                        );
                    }
                }
            }
        }
    }

    private AudioManager.AudioRecordingCallback mAudioRecordingCallback =
            new AudioManager.AudioRecordingCallback() {
        @Override
        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
            synchronized (mActiveItems) {
                mRecordingsByUid.clear();
                final int recordingsCount = configs.size();
                for (int i = 0; i < recordingsCount; i++) {
                    AudioRecordingConfiguration recording = configs.get(i);

                    ArrayList<AudioRecordingConfiguration> recordings = mRecordingsByUid.get(
                            recording.getClientUid());
                    if (recordings == null) {
                        recordings = new ArrayList<>();
                        mRecordingsByUid.put(recording.getClientUid(), recordings);
                    }
                    recordings.add(recording);
                }
            }
            updateRecordingPausedStatus();
        }
    };

    protected class H extends Handler {
        H(Looper looper) {
            super(looper);
+106 −1
Original line number Diff line number Diff line
@@ -27,6 +27,9 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -34,6 +37,8 @@ import static org.mockito.Mockito.when;

import android.app.AppOpsManager;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.AudioRecordingConfiguration;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
@@ -47,9 +52,11 @@ import com.android.systemui.dump.DumpManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Collections;
import java.util.List;

@SmallTest
@@ -73,6 +80,12 @@ public class AppOpsControllerTest extends SysuiTestCase {
    private PermissionFlagsCache mFlagsCache;
    @Mock
    private PackageManager mPackageManager;
    @Mock(stubOnly = true)
    private AudioManager mAudioManager;
    @Mock(stubOnly = true)
    private AudioManager.AudioRecordingCallback mRecordingCallback;
    @Mock(stubOnly = true)
    private AudioRecordingConfiguration mPausedMockRecording;

    private AppOpsControllerImpl mController;
    private TestableLooper mTestableLooper;
@@ -94,11 +107,20 @@ public class AppOpsControllerTest extends SysuiTestCase {
        when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
                eq(TEST_UID_NON_USER_SENSITIVE))).thenReturn(0);

        doAnswer((invocation) -> mRecordingCallback = invocation.getArgument(0))
                .when(mAudioManager).registerAudioRecordingCallback(any(), any());
        when(mPausedMockRecording.getClientUid()).thenReturn(TEST_UID);
        when(mPausedMockRecording.isClientSilenced()).thenReturn(true);

        when(mAudioManager.getActiveRecordingConfigurations())
                .thenReturn(List.of(mPausedMockRecording));

        mController = new AppOpsControllerImpl(
                mContext,
                mTestableLooper.getLooper(),
                mDumpManager,
                mFlagsCache
                mFlagsCache,
                mAudioManager
        );
    }

@@ -363,6 +385,89 @@ public class AppOpsControllerTest extends SysuiTestCase {
                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
    }

    @Test
    public void testPausedRecordingIsRetrievedOnCreation() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
        mTestableLooper.processAllMessages();

        mController.onOpActiveChanged(
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();

        verify(mCallback, never())
                .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean());
    }

    @Test
    public void testPausedRecordingFilteredOut() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
        mTestableLooper.processAllMessages();

        mController.onOpActiveChanged(
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();

        assertTrue(mController.getActiveAppOps().isEmpty());
    }

    @Test
    public void testOnlyRecordAudioPaused() {
        mController.addCallback(new int[]{
                AppOpsManager.OP_RECORD_AUDIO,
                AppOpsManager.OP_CAMERA
        }, mCallback);
        mTestableLooper.processAllMessages();

        mController.onOpActiveChanged(
                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();

        verify(mCallback).onActiveStateChanged(
                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
        List<AppOpItem> list = mController.getActiveAppOps();

        assertEquals(1, list.size());
        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
    }

    @Test
    public void testUnpausedRecordingSentActive() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
        mTestableLooper.processAllMessages();
        mController.onOpActiveChanged(
                AppOpsManager.OP_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);
    }

    @Test
    public void testAudioPausedSentInactive() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
        mTestableLooper.processAllMessages();
        mController.onOpActiveChanged(
                AppOpsManager.OP_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);
    }

    private class TestHandler extends AppOpsControllerImpl.H {
        TestHandler(Looper looper) {
            mController.super(looper);