Add Setting kill switch for mic disclosure

Disable if mic disclosure if global settings
"sysui_mic_disclosure_enable" is 0. If the setting is not present -
enable.

Bug: 162308361
Test: adb shell appops start com.google.android.katniss 27 # indicator
comes up
      adb shell settings put global sysui_mic_disclosure_enable 0 #
indicator disappears
      adb shell appops stop com.google.android.katniss 27
      adb shell appops start com.google.android.katniss 27 # indicator
does not come up
      adb shell settings put global sysui_mic_disclosure_enable 1
      adb shell appops start com.google.android.katniss 27 # indicator
comes up

Change-Id: I7317fe58676615578a07f0b37ae5a8cd99db5576
This commit is contained in:
Sergey Nikolaienkov
2020-08-07 09:22:29 +00:00
parent 2ea424eac6
commit 7921255fb1
4 changed files with 166 additions and 40 deletions

View File

@@ -40,5 +40,9 @@ abstract class AudioActivityObserver {
mListener = listener;
}
abstract void start();
abstract void stop();
abstract Set<String> getActivePackages();
}

View File

@@ -28,6 +28,7 @@ import android.annotation.UiThread;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.provider.Settings;
import android.text.TextUtils;
@@ -65,11 +66,13 @@ public class AudioRecordingDisclosureBar implements
// CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
private static final String ENABLE_FLAG = "sysui_mic_disclosure_enable";
private static final String EXEMPT_PACKAGES_LIST = "sysui_mic_disclosure_exempt";
private static final String FORCED_PACKAGES_LIST = "sysui_mic_disclosure_forced";
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"STATE_"}, value = {
STATE_STOPPED,
STATE_NOT_SHOWN,
STATE_APPEARING,
STATE_SHOWN,
@@ -80,6 +83,7 @@ public class AudioRecordingDisclosureBar implements
})
public @interface State {}
private static final int STATE_STOPPED = -1;
private static final int STATE_NOT_SHOWN = 0;
private static final int STATE_APPEARING = 1;
private static final int STATE_SHOWN = 2;
@@ -94,6 +98,7 @@ public class AudioRecordingDisclosureBar implements
private static final float PULSE_SCALE = 1.25f;
private final Context mContext;
private boolean mIsEnabledInSettings;
private View mIndicatorView;
private View mIconTextsContainer;
@@ -104,13 +109,13 @@ public class AudioRecordingDisclosureBar implements
private TextView mTextView;
private boolean mIsLtr;
@State private int mState = STATE_NOT_SHOWN;
@State private int mState = STATE_STOPPED;
/**
* Array of the observers that monitor different aspects of the system, such as AppOps and
* microphone foreground services
*/
private final AudioActivityObserver[] mAudioActivityObservers;
private AudioActivityObserver[] mAudioActivityObservers;
/**
* Whether the indicator should expand and show the recording application's label.
* If disabled ({@code false}) the "minimized" ({@link #STATE_MINIMIZED}) indicator would appear
@@ -144,6 +149,7 @@ public class AudioRecordingDisclosureBar implements
public AudioRecordingDisclosureBar(Context context) {
mContext = context;
// Loading configs
mRevealRecordingPackages = mContext.getResources().getBoolean(
R.bool.audio_recording_disclosure_reveal_packages);
mExemptPackages = new ArraySet<>(
@@ -152,10 +158,52 @@ public class AudioRecordingDisclosureBar implements
mExemptPackages.addAll(Arrays.asList(getGlobalStringArray(EXEMPT_PACKAGES_LIST)));
mExemptPackages.removeAll(Arrays.asList(getGlobalStringArray(FORCED_PACKAGES_LIST)));
mAudioActivityObservers = new AudioActivityObserver[]{
new RecordAudioAppOpObserver(mContext, this),
new MicrophoneForegroundServicesObserver(mContext, this),
};
// Check setting, and start if enabled
mIsEnabledInSettings = checkIfEnabledInSettings();
registerSettingsObserver();
if (mIsEnabledInSettings) {
start();
}
}
@UiThread
private void start() {
if (mState != STATE_STOPPED) {
return;
}
mState = STATE_NOT_SHOWN;
if (mAudioActivityObservers == null) {
mAudioActivityObservers = new AudioActivityObserver[]{
new RecordAudioAppOpObserver(mContext, this),
new MicrophoneForegroundServicesObserver(mContext, this),
};
}
for (int i = mAudioActivityObservers.length - 1; i >= 0; i--) {
mAudioActivityObservers[i].start();
}
}
@UiThread
private void stop() {
if (mState == STATE_STOPPED) {
return;
}
mState = STATE_STOPPED;
for (int i = mAudioActivityObservers.length - 1; i >= 0; i--) {
mAudioActivityObservers[i].stop();
}
// Remove the view if shown.
if (mState != STATE_NOT_SHOWN) {
removeIndicatorView();
}
// Clean up the state.
mSessionNotifiedPackages.clear();
mPendingNotificationPackages.clear();
}
@UiThread
@@ -213,7 +261,6 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void hideIndicatorIfNeeded() {
if (DEBUG) Log.d(TAG, "hideIndicatorIfNeeded");
// If not MINIMIZED, will check whether the indicator should be hidden when the indicator
// comes to the STATE_MINIMIZED eventually.
if (mState != STATE_MINIMIZED) return;
@@ -222,7 +269,6 @@ public class AudioRecordingDisclosureBar implements
for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
if (mExemptPackages.contains(activePackage)) continue;
if (DEBUG) Log.d(TAG, " - there are still ongoing activities");
return;
}
}
@@ -235,7 +281,7 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void show(String packageName) {
if (DEBUG) {
Log.d(TAG, "Showing indicator for " + packageName);
Log.d(TAG, "Showing indicator");
}
mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection()
@@ -286,6 +332,10 @@ public class AudioRecordingDisclosureBar implements
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (mState == STATE_STOPPED) {
return;
}
// Remove the observer
mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
this);
@@ -306,6 +356,10 @@ public class AudioRecordingDisclosureBar implements
@Override
public void onAnimationStart(Animator animation,
boolean isReverse) {
if (mState == STATE_STOPPED) {
return;
}
// Indicator is INVISIBLE at the moment, change it.
mIndicatorView.setVisibility(View.VISIBLE);
}
@@ -345,9 +399,6 @@ public class AudioRecordingDisclosureBar implements
assertRevealingRecordingPackages();
final String label = getApplicationLabel(packageName);
if (DEBUG) {
Log.d(TAG, "Expanding for " + packageName + " (" + label + ")...");
}
mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
final AnimatorSet set = new AnimatorSet();
@@ -373,7 +424,6 @@ public class AudioRecordingDisclosureBar implements
private void minimize() {
assertRevealingRecordingPackages();
if (DEBUG) Log.d(TAG, "Minimizing...");
final int targetOffset = (mIsLtr ? 1 : -1) * mTextsContainers.getWidth();
final AnimatorSet set = new AnimatorSet();
set.playTogether(
@@ -396,7 +446,9 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void hide() {
if (DEBUG) Log.d(TAG, "Hiding...");
if (DEBUG) {
Log.d(TAG, "Hide indicator");
}
final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth()
- (int) mIconTextsContainer.getTranslationX());
final AnimatorSet set = new AnimatorSet();
@@ -418,9 +470,12 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void onExpanded() {
if (mState == STATE_STOPPED) {
return;
}
assertRevealingRecordingPackages();
if (DEBUG) Log.d(TAG, "Expanded");
mState = STATE_SHOWN;
mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
@@ -428,7 +483,10 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void onMinimized() {
if (DEBUG) Log.d(TAG, "Minimized");
if (mState == STATE_STOPPED) {
return;
}
mState = STATE_MINIMIZED;
if (mRevealRecordingPackages) {
@@ -443,8 +501,21 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void onHidden() {
if (DEBUG) Log.d(TAG, "Hidden");
if (mState == STATE_STOPPED) {
return;
}
removeIndicatorView();
mState = STATE_NOT_SHOWN;
// Check if anybody started recording while we were in STATE_DISAPPEARING
if (!mPendingNotificationPackages.isEmpty()) {
// There is a new application that started recording, tell the user about it.
show(mPendingNotificationPackages.poll());
}
}
private void removeIndicatorView() {
final WindowManager windowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
windowManager.removeView(mIndicatorView);
@@ -456,14 +527,6 @@ public class AudioRecordingDisclosureBar implements
mTextsContainers = null;
mTextView = null;
mBgEnd = null;
mState = STATE_NOT_SHOWN;
// Check if anybody started recording while we were in STATE_DISAPPEARING
if (!mPendingNotificationPackages.isEmpty()) {
// There is a new application that started recording, tell the user about it.
show(mPendingNotificationPackages.poll());
}
}
@UiThread
@@ -504,4 +567,33 @@ public class AudioRecordingDisclosureBar implements
DEBUG ? new RuntimeException("Should not be called") : null);
}
}
private boolean checkIfEnabledInSettings() {
// 0 = disabled, everything else = enabled. Enabled by default.
return Settings.Global.getInt(mContext.getContentResolver(),
ENABLE_FLAG, 1) != 0;
}
private void registerSettingsObserver() {
final ContentObserver contentObserver = new ContentObserver(
mContext.getMainThreadHandler()) {
@Override
public void onChange(boolean selfChange) {
if (mIsEnabledInSettings == checkIfEnabledInSettings()) {
// Nothing changed as we know it - ignore.
return;
}
// Things changed: flip the flag.
mIsEnabledInSettings = !mIsEnabledInSettings;
if (mIsEnabledInSettings) {
start();
} else {
stop();
}
}
};
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(ENABLE_FLAG), false, contentObserver);
}
}

View File

@@ -30,7 +30,6 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -41,9 +40,8 @@ import java.util.Set;
*/
class MicrophoneForegroundServicesObserver extends AudioActivityObserver {
private static final String TAG = "MicrophoneForegroundServicesObserver";
private static final boolean ENABLED = true;
private final IActivityManager mActivityManager;
private IActivityManager mActivityManager;
/**
* A dictionary that maps PIDs to the package names. We only keep track of the PIDs that are
* "active" (those that are running FGS with FOREGROUND_SERVICE_TYPE_MICROPHONE flag).
@@ -60,7 +58,10 @@ class MicrophoneForegroundServicesObserver extends AudioActivityObserver {
MicrophoneForegroundServicesObserver(Context context,
OnAudioActivityStateChangeListener listener) {
super(context, listener);
}
@Override
void start() {
mActivityManager = ActivityManager.getService();
try {
mActivityManager.registerProcessObserver(mProcessObserver);
@@ -69,9 +70,20 @@ class MicrophoneForegroundServicesObserver extends AudioActivityObserver {
}
}
@Override
void stop() {
try {
mActivityManager.unregisterProcessObserver(mProcessObserver);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't unregister process observer", e);
}
mActivityManager = null;
mPackageToProcessCount.clear();
}
@Override
Set<String> getActivePackages() {
return ENABLED ? mPackageToProcessCount.keySet() : Collections.emptySet();
return mPackageToProcessCount.keySet();
}
@UiThread
@@ -141,13 +153,12 @@ class MicrophoneForegroundServicesObserver extends AudioActivityObserver {
@UiThread
private void notifyPackageStateChanged(String packageName, boolean active) {
if (active) {
if (DEBUG) Log.d(TAG, "New microphone fgs detected, package=" + packageName);
} else {
if (DEBUG) Log.d(TAG, "Microphone fgs is gone, package=" + packageName);
if (DEBUG) {
Log.d(TAG, (active ? "New microphone fgs detected" : "Microphone fgs is gone")
+ ", package=" + packageName);
}
if (ENABLED) mListener.onAudioActivityStateChange(active, packageName);
mListener.onAudioActivityStateChange(active, packageName);
}
@UiThread

View File

@@ -42,14 +42,33 @@ class RecordAudioAppOpObserver extends AudioActivityObserver implements
RecordAudioAppOpObserver(Context context, OnAudioActivityStateChangeListener listener) {
super(context, listener);
}
@Override
void start() {
if (DEBUG) {
Log.d(TAG, "Start");
}
// Register AppOpsManager callback
final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService(
Context.APP_OPS_SERVICE);
appOpsManager.startWatchingActive(
new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
mContext.getMainExecutor(),
this);
mContext.getSystemService(AppOpsManager.class)
.startWatchingActive(
new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
mContext.getMainExecutor(),
this);
}
@Override
void stop() {
if (DEBUG) {
Log.d(TAG, "Stop");
}
// Unregister AppOpsManager callback
mContext.getSystemService(AppOpsManager.class).stopWatchingActive(this);
// Clean up state
mActiveAudioRecordingPackages.clear();
}
@UiThread