diff options
4 files changed, 166 insertions, 40 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java index 87b3956060f3..bbab6253a4d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java @@ -40,5 +40,9 @@ abstract class AudioActivityObserver { mListener = listener; } + abstract void start(); + + abstract void stop(); + abstract Set<String> getActivePackages(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java index 8e4e12358836..65f533e6a23d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java @@ -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); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java index 1ede88a26020..8caf95fb48f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java @@ -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); @@ -70,8 +71,19 @@ 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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java index b5b1c2b3018a..9a2b4a93ac89 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java @@ -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 |