diff --git a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml b/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml deleted file mode 100644 index 55d21de00ca30..0000000000000 --- a/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/tv_circle_dark.xml b/packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml similarity index 74% rename from packages/SystemUI/res/drawable/tv_circle_dark.xml rename to packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml index d1ba8e71ec313..93f8724b22a99 100644 --- a/packages/SystemUI/res/drawable/tv_circle_dark.xml +++ b/packages/SystemUI/res/drawable/tv_rect_shadow_rounded.xml @@ -16,9 +16,10 @@ --> + android:shape="rectangle"> - + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/tv_ring_white.xml b/packages/SystemUI/res/drawable/tv_ring_white.xml deleted file mode 100644 index 0f7cc1082f715..0000000000000 --- a/packages/SystemUI/res/drawable/tv_ring_white.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml index 1d2340dadb8a6..f9336a5403763 100644 --- a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml +++ b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml @@ -19,7 +19,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" - android:padding="32dp"> + android:padding="12dp"> + android:layout_width="wrap_content" + android:layout_height="match_parent"> - - + android:layout_width="34dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:background="@drawable/tv_rect_shadow_rounded"> - - - - diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml index 53cd9716c98e5..c92762c017f4c 100644 --- a/packages/SystemUI/res/values/colors_tv.xml +++ b/packages/SystemUI/res/values/colors_tv.xml @@ -24,7 +24,8 @@ #FF3C4043 - #4DFFFFFF + #CC000000 + #33FFFFFF #FFCC0000 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 87b3956060f3b..bbab6253a4d18 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 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 8e4e123588360..e3eed35f04837 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 @@ -22,12 +22,12 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; import android.annotation.IntDef; 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 +65,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 +82,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; @@ -90,10 +93,9 @@ public class AudioRecordingDisclosureBar implements private static final int ANIMATION_DURATION = 600; private static final int MAXIMIZED_DURATION = 3000; - private static final int PULSE_BIT_DURATION = 1000; - private static final float PULSE_SCALE = 1.25f; private final Context mContext; + private boolean mIsEnabledInSettings; private View mIndicatorView; private View mIconTextsContainer; @@ -104,13 +106,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 +146,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 +155,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 +258,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 +266,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 +278,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 +329,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,13 +353,16 @@ 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); } @Override public void onAnimationEnd(Animator animation) { - startPulsatingAnimation(); if (mRevealRecordingPackages) { onExpanded(); } else { @@ -345,9 +395,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 +420,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 +442,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 +466,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 +479,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 +497,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,28 +523,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 - private void startPulsatingAnimation() { - final View pulsatingView = mIconTextsContainer.findViewById(R.id.pulsating_circle); - final ObjectAnimator animator = - ObjectAnimator.ofPropertyValuesHolder( - pulsatingView, - PropertyValuesHolder.ofFloat(View.SCALE_X, PULSE_SCALE), - PropertyValuesHolder.ofFloat(View.SCALE_Y, PULSE_SCALE)); - animator.setDuration(PULSE_BIT_DURATION); - animator.setRepeatCount(ObjectAnimator.INFINITE); - animator.setRepeatMode(ObjectAnimator.REVERSE); - animator.start(); } private String[] getGlobalStringArray(String setting) { @@ -504,4 +549,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 1ede88a260207..8caf95fb48f5e 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); @@ -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 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 b5b1c2b3018a4..9a2b4a93ac898 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