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