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

Commit 13b297b0 authored by Nate Myren's avatar Nate Myren Committed by Automerger Merge Worker
Browse files

Merge "Hide indicators when camera/mic disabled" into sc-dev am: 9eba46e7 am: ba193d43

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13418589

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I7b9a02ef627a778725544f5053033e9835662860
parents 28441e5f ba193d43
Loading
Loading
Loading
Loading
+6 −7
Original line number Diff line number Diff line
@@ -26,8 +26,7 @@ public class AppOpItem {
    private String mPackageName;
    private long mTimeStarted;
    private StringBuilder mState;
    // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
    private boolean mSilenced;
    private boolean mIsDisabled;

    public AppOpItem(int code, int uid, String packageName, long timeStarted) {
        this.mCode = code;
@@ -58,16 +57,16 @@ public class AppOpItem {
        return mTimeStarted;
    }

    public void setSilenced(boolean silenced) {
        mSilenced = silenced;
    public void setDisabled(boolean misDisabled) {
        this.mIsDisabled = misDisabled;
    }

    public boolean isSilenced() {
        return mSilenced;
    public boolean isDisabled() {
        return mIsDisabled;
    }

    @Override
    public String toString() {
        return mState.append(mSilenced).append(")").toString();
        return mState.append(mIsDisabled).append(")").toString();
    }
}
+56 −20
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.appops;

import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;

import android.Manifest;
@@ -45,6 +47,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.util.Assert;

import java.io.FileDescriptor;
@@ -64,7 +67,8 @@ import javax.inject.Inject;
@SysUISingleton
public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
        AppOpsManager.OnOpActiveChangedInternalListener,
        AppOpsManager.OnOpNotedListener, Dumpable {
        AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback,
        Dumpable {

    // This is the minimum time that we will keep AppOps that are noted on record. If multiple
    // occurrences of the same (op, package, uid) happen in a shorter interval, they will not be
@@ -77,8 +81,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
    private final AppOpsManager mAppOps;
    private final AudioManager mAudioManager;
    private final LocationManager mLocationManager;
    // TODO ntmyren: remove t
    private final PackageManager mPackageManager;
    private final IndividualSensorPrivacyController mSensorPrivacyController;

    // mLocationProviderPackages are cached and updated only occasionally
    private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
@@ -91,6 +95,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
    private final PermissionFlagsCache mFlagsCache;
    private boolean mListening;
    private boolean mMicMuted;
    private boolean mCameraDisabled;

    @GuardedBy("mActiveItems")
    private final List<AppOpItem> mActiveItems = new ArrayList<>();
@@ -118,6 +123,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
            DumpManager dumpManager,
            PermissionFlagsCache cache,
            AudioManager audioManager,
            IndividualSensorPrivacyController sensorPrivacyController,
            BroadcastDispatcher dispatcher
    ) {
        mDispatcher = dispatcher;
@@ -129,7 +135,10 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
            mCallbacksByCode.put(OPS[i], new ArraySet<>());
        }
        mAudioManager = audioManager;
        mMicMuted = audioManager.isMicrophoneMute();
        mSensorPrivacyController = sensorPrivacyController;
        mMicMuted = audioManager.isMicrophoneMute()
                || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
        mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA);
        mLocationManager = context.getSystemService(LocationManager.class);
        mPackageManager = context.getPackageManager();
        dumpManager.registerDumpable(TAG, this);
@@ -147,6 +156,12 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
            mAppOps.startWatchingActive(OPS, this);
            mAppOps.startWatchingNoted(OPS, this);
            mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
            mSensorPrivacyController.addCallback(this);

            mMicMuted = mAudioManager.isMicrophoneMute()
                    || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
            mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA);

            mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
                    mAudioManager.getActiveRecordingConfigurations()));
            mDispatcher.registerReceiverWithHandler(this,
@@ -156,6 +171,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
            mAppOps.stopWatchingActive(this);
            mAppOps.stopWatchingNoted(this);
            mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
            mSensorPrivacyController.removeCallback(this);

            mBGHandler.removeCallbacksAndMessages(null); // null removes all
            mDispatcher.unregisterReceiver(this);
@@ -235,11 +251,13 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
            if (item == null && active) {
                item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
                if (code == AppOpsManager.OP_RECORD_AUDIO) {
                    item.setSilenced(isAnyRecordingPausedLocked(uid));
                    item.setDisabled(isAnyRecordingPausedLocked(uid));
                } else if (code == AppOpsManager.OP_CAMERA) {
                    item.setDisabled(mCameraDisabled);
                }
                mActiveItems.add(item);
                if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
                return !item.isSilenced();
                return !item.isDisabled();
            } else if (item != null && !active) {
                mActiveItems.remove(item);
                if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -409,7 +427,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
                AppOpItem item = mActiveItems.get(i);
                if ((userId == UserHandle.USER_ALL
                        || UserHandle.getUserId(item.getUid()) == userId)
                        && isUserVisible(item) && !item.isSilenced()) {
                        && isUserVisible(item) && !item.isDisabled()) {
                    list.add(item);
                }
            }
@@ -512,26 +530,31 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
        return false;
    }

    private void updateRecordingPausedStatus() {
    private void updateSensorDisabledStatus() {
        synchronized (mActiveItems) {
            int size = mActiveItems.size();
            for (int i = 0; i < size; i++) {
                AppOpItem item = mActiveItems.get(i);

                boolean paused = false;
                if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
                    boolean paused = isAnyRecordingPausedLocked(item.getUid());
                    if (item.isSilenced() != paused) {
                        item.setSilenced(paused);
                    paused = isAnyRecordingPausedLocked(item.getUid());
                } else if (item.getCode() == AppOpsManager.OP_CAMERA) {
                    paused = mCameraDisabled;
                }

                if (item.isDisabled() != paused) {
                    item.setDisabled(paused);
                    notifySuscribers(
                            item.getCode(),
                            item.getUid(),
                            item.getPackageName(),
                                !item.isSilenced()
                            !item.isDisabled()
                    );
                }
            }
        }
    }
    }

    private AudioManager.AudioRecordingCallback mAudioRecordingCallback =
            new AudioManager.AudioRecordingCallback() {
@@ -552,14 +575,27 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
                    recordings.add(recording);
                }
            }
            updateRecordingPausedStatus();
            updateSensorDisabledStatus();
        }
    };

    @Override
    public void onReceive(Context context, Intent intent) {
        mMicMuted = mAudioManager.isMicrophoneMute();
        updateRecordingPausedStatus();
        mMicMuted = mAudioManager.isMicrophoneMute()
                || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
        updateSensorDisabledStatus();
    }

    @Override
    public void onSensorBlockedChanged(int sensor, boolean blocked) {
        mBGHandler.post(() -> {
            if (sensor == INDIVIDUAL_SENSOR_CAMERA) {
                mCameraDisabled = blocked;
            } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) {
                mMicMuted = mAudioManager.isMicrophoneMute() || blocked;
            }
            updateSensorDisabledStatus();
        });
    }

    protected class H extends Handler {
+4 −3
Original line number Diff line number Diff line
@@ -16,8 +16,8 @@

package com.android.systemui.statusbar.policy;

import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;

import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.IndividualSensor;
@@ -30,7 +30,8 @@ import java.util.Set;

public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {

    private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE};
    private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA,
            INDIVIDUAL_SENSOR_MICROPHONE};

    private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
    private final SparseBooleanArray mState = new SparseBooleanArray();
+79 −0
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.systemui.appops;

import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;

import static junit.framework.TestCase.assertFalse;

import static org.junit.Assert.assertEquals;
@@ -49,6 +52,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;

import org.junit.Before;
import org.junit.Test;
@@ -81,6 +85,8 @@ public class AppOpsControllerTest extends SysuiTestCase {
    private PermissionFlagsCache mFlagsCache;
    @Mock
    private PackageManager mPackageManager;
    @Mock
    private IndividualSensorPrivacyController mSensorPrivacyController;
    @Mock(stubOnly = true)
    private AudioManager mAudioManager;
    @Mock()
@@ -118,12 +124,18 @@ public class AppOpsControllerTest extends SysuiTestCase {
        when(mAudioManager.getActiveRecordingConfigurations())
                .thenReturn(List.of(mPausedMockRecording));

        when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
                .thenReturn(false);
        when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
                .thenReturn(false);

        mController = new AppOpsControllerImpl(
                mContext,
                mTestableLooper.getLooper(),
                mDumpManager,
                mFlagsCache,
                mAudioManager,
                mSensorPrivacyController,
                mDispatcher
        );
    }
@@ -133,6 +145,7 @@ public class AppOpsControllerTest extends SysuiTestCase {
        mController.setListening(true);
        verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
        verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
        verify(mSensorPrivacyController, times(1)).addCallback(mController);
    }

    @Test
@@ -140,6 +153,7 @@ public class AppOpsControllerTest extends SysuiTestCase {
        mController.setListening(false);
        verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
        verify(mDispatcher, times(1)).unregisterReceiver(mController);
        verify(mSensorPrivacyController, times(1)).removeCallback(mController);
    }

    @Test
@@ -476,6 +490,71 @@ public class AppOpsControllerTest extends SysuiTestCase {
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
    }

    @Test
    public void testAudioFilteredWhenMicDisabled() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
                mCallback);
        mTestableLooper.processAllMessages();
        mController.onOpActiveChanged(
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();
        List<AppOpItem> list = mController.getActiveAppOps();
        assertEquals(1, list.size());
        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
        assertFalse(list.get(0).isDisabled());

        // Add a camera op, and disable the microphone. The camera op should be the only op returned
        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, true);
        mController.onOpActiveChanged(
                AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();
        list = mController.getActiveAppOps();
        assertEquals(1, list.size());
        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());


        // Re enable the microphone, and verify the op returns
        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, false);
        mTestableLooper.processAllMessages();

        list = mController.getActiveAppOps();
        assertEquals(2, list.size());
        int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(micIdx).getCode());
    }

    @Test
    public void testCameraFilteredWhenCameraDisabled() {
        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
                mCallback);
        mTestableLooper.processAllMessages();
        mController.onOpActiveChanged(
                AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();
        List<AppOpItem> list = mController.getActiveAppOps();
        assertEquals(1, list.size());
        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
        assertFalse(list.get(0).isDisabled());

        // Add an audio op, and disable the camera. The audio op should be the only op returned
        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, true);
        mController.onOpActiveChanged(
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
        mTestableLooper.processAllMessages();
        list = mController.getActiveAppOps();
        assertEquals(1, list.size());
        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());

        // Re enable the camera, and verify the op returns
        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, false);
        mTestableLooper.processAllMessages();

        list = mController.getActiveAppOps();
        assertEquals(2, list.size());
        int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 0 : 1;
        assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode());
    }

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