diff --git a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
deleted file mode 100644
index b62018d7cb9e8..0000000000000
--- a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/SystemUI/res/layout/tv_ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/tv_ongoing_privacy_chip.xml
new file mode 100644
index 0000000000000..dff148b2c5705
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_ongoing_privacy_chip.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 722f1480463dd..b02d8b8cf7e42 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -28,6 +28,7 @@
- com.android.systemui.util.NotificationChannels
- com.android.systemui.volume.VolumeUI
+ - com.android.systemui.privacy.television.TvOngoingPrivacyChip
- com.android.systemui.statusbar.tv.TvStatusBar
- com.android.systemui.statusbar.tv.notifications.TvNotificationPanel
- com.android.systemui.statusbar.tv.notifications.TvNotificationHandler
diff --git a/packages/SystemUI/res/values-television/dimens.xml b/packages/SystemUI/res/values-television/dimens.xml
index 6da0c693f3897..7626db93dd762 100644
--- a/packages/SystemUI/res/values-television/dimens.xml
+++ b/packages/SystemUI/res/values-television/dimens.xml
@@ -17,4 +17,8 @@
- 1.0
+
+ 3dp
+ 8dp
+ 13dp
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml
deleted file mode 100644
index 2e776749582c6..0000000000000
--- a/packages/SystemUI/res/values/config_tv.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
- true
-
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index e5c9d104ea93e..1ed881993800b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.media.systemsounds.HomeSoundEffectController;
import com.android.systemui.power.PowerUI;
+import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsModule;
import com.android.systemui.shortcut.ShortcutKeyDispatcher;
@@ -154,6 +155,12 @@ public abstract class SystemUIBinder {
@ClassKey(TvNotificationPanel.class)
public abstract SystemUI bindsTvNotificationPanel(TvNotificationPanel sysui);
+ /** Inject into TvOngoingPrivacyChip. */
+ @Binds
+ @IntoMap
+ @ClassKey(TvOngoingPrivacyChip.class)
+ public abstract SystemUI bindsTvOngoingPrivacyChip(TvOngoingPrivacyChip sysui);
+
/** Inject into VolumeUI. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
similarity index 50%
rename from packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
rename to packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
index c9d1b71bca777..0fa7b59d0e54f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
+++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.tv.micdisclosure;
+package com.android.systemui.privacy.television;
-import static android.provider.DeviceConfig.NAMESPACE_PRIVACY;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.animation.Animator;
@@ -25,57 +24,56 @@ import android.animation.ObjectAnimator;
import android.annotation.IntDef;
import android.annotation.UiThread;
import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.PixelFormat;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
-import android.util.ArraySet;
+import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
import com.android.systemui.R;
-import com.android.systemui.statusbar.tv.TvStatusBar;
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.privacy.PrivacyChipBuilder;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
-import java.util.Set;
+
+import javax.inject.Inject;
/**
- * A component of {@link TvStatusBar} responsible for notifying the user whenever an application is
- * recording audio.
- *
- * @see TvStatusBar
+ * A SystemUI component responsible for notifying the user whenever an application is
+ * recording audio, accessing the camera or accessing the location.
*/
-public class AudioRecordingDisclosureBar implements
- AudioActivityObserver.OnAudioActivityStateChangeListener {
- private static final String TAG = "AudioRecordingDisclosure";
+@SysUISingleton
+public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemController.Callback {
+ private static final String TAG = "TvOngoingPrivacyChip";
static final boolean DEBUG = false;
- // This title is used to test the microphone disclosure indicator in
- // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
+ // This title is used in CameraMicIndicatorsPermissionTest and
+ // RecognitionServiceMicIndicatorTest.
private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
- private static final String ENABLED_FLAG = "mic_disclosure_enabled";
- private static final String EXEMPT_PACKAGES_LIST = "mic_disclosure_exempt_packages";
- private static final String FORCED_PACKAGES_LIST = "mic_disclosure_forced_packages";
-
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"STATE_"}, value = {
- STATE_STOPPED,
STATE_NOT_SHOWN,
STATE_APPEARING,
STATE_SHOWN,
STATE_DISAPPEARING
})
- public @interface State {}
+ 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;
@@ -84,106 +82,89 @@ public class AudioRecordingDisclosureBar implements
private static final int ANIMATION_DURATION_MS = 200;
private final Context mContext;
- private boolean mIsEnabled;
+ private final PrivacyItemController mPrivacyItemController;
private View mIndicatorView;
private boolean mViewAndWindowAdded;
private ObjectAnimator mAnimator;
- @State private int mState = STATE_STOPPED;
+ private boolean mAllIndicatorsFlagEnabled;
+ private boolean mMicCameraIndicatorFlagEnabled;
+ private boolean mLocationIndicatorEnabled;
+ private List mPrivacyItems;
- /**
- * Array of the observers that monitor different aspects of the system, such as AppOps and
- * microphone foreground services
- */
- private AudioActivityObserver[] mAudioActivityObservers;
- /**
- * Set of applications for which we make an exception and do not show the indicator. This gets
- * populated once - in {@link #AudioRecordingDisclosureBar(Context)}.
- */
- private final Set mExemptPackages = new ArraySet<>();
+ private LinearLayout mIconsContainer;
+ private final int mIconSize;
+ private final int mIconMarginStart;
- public AudioRecordingDisclosureBar(Context context) {
+ @State
+ private int mState = STATE_NOT_SHOWN;
+
+ @Inject
+ public TvOngoingPrivacyChip(Context context, PrivacyItemController privacyItemController) {
+ super(context);
+ Log.d(TAG, "Privacy chip running without id");
mContext = context;
+ mPrivacyItemController = privacyItemController;
- // Load configs
- reloadExemptPackages();
+ Resources res = mContext.getResources();
+ mIconMarginStart = Math.round(res.getDimension(R.dimen.privacy_chip_icon_margin));
+ mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size);
- mIsEnabled = DeviceConfig.getBoolean(NAMESPACE_PRIVACY, ENABLED_FLAG, true);
- // Start if enabled
- if (mIsEnabled) {
- start();
- }
+ mAllIndicatorsFlagEnabled = privacyItemController.getAllIndicatorsAvailable();
+ mMicCameraIndicatorFlagEnabled = privacyItemController.getMicCameraAvailable();
+ mLocationIndicatorEnabled = privacyItemController.getLocationAvailable();
- // Set up a config change listener
- DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_PRIVACY, mContext.getMainExecutor(),
- mConfigChangeListener);
- }
-
- private void reloadExemptPackages() {
- mExemptPackages.clear();
- mExemptPackages.addAll(Arrays.asList(mContext.getResources().getStringArray(
- R.array.audio_recording_disclosure_exempt_apps)));
- mExemptPackages.addAll(
- splitByComma(
- DeviceConfig.getString(NAMESPACE_PRIVACY, EXEMPT_PACKAGES_LIST, null)));
- mExemptPackages.removeAll(
- splitByComma(
- DeviceConfig.getString(NAMESPACE_PRIVACY, FORCED_PACKAGES_LIST, null)));
- }
-
- @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();
- }
- }
-
- @UiThread
- @Override
- public void onAudioActivityStateChange(boolean active, String packageName) {
if (DEBUG) {
- Log.d(TAG,
- "onAudioActivityStateChange, packageName=" + packageName + ", active="
- + active);
+ Log.d(TAG, "allIndicators: " + mAllIndicatorsFlagEnabled);
+ Log.d(TAG, "micCameraIndicators: " + mMicCameraIndicatorFlagEnabled);
+ Log.d(TAG, "locationIndicators: " + mLocationIndicatorEnabled);
}
+ }
- if (mExemptPackages.contains(packageName)) {
- if (DEBUG) Log.d(TAG, " - exempt package: ignoring");
- return;
- }
+ @Override
+ public void start() {
+ mPrivacyItemController.addCallback(this);
+ }
- if (active) {
- showIfNeeded();
+ @Override
+ public void onPrivacyItemsChanged(List privacyItems) {
+ if (DEBUG) Log.d(TAG, "PrivacyItemsChanged");
+ mPrivacyItems = privacyItems;
+ updateUI();
+ }
+
+ @Override
+ public void onFlagAllChanged(boolean flag) {
+ if (DEBUG) Log.d(TAG, "all indicators enabled: " + flag);
+ mAllIndicatorsFlagEnabled = flag;
+ }
+
+ @Override
+ public void onFlagMicCameraChanged(boolean flag) {
+ if (DEBUG) Log.d(TAG, "mic/camera indicators enabled: " + flag);
+ mMicCameraIndicatorFlagEnabled = flag;
+ }
+
+ @Override
+ public void onFlagLocationChanged(boolean flag) {
+ if (DEBUG) Log.d(TAG, "location indicators enabled: " + flag);
+ mLocationIndicatorEnabled = flag;
+ }
+
+ private void updateUI() {
+ if (DEBUG) Log.d(TAG, mPrivacyItems.size() + " privacy items");
+
+ if ((mMicCameraIndicatorFlagEnabled || mAllIndicatorsFlagEnabled
+ || mLocationIndicatorEnabled) && !mPrivacyItems.isEmpty()) {
+ if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) {
+ showIndicator();
+ } else {
+ if (DEBUG) Log.d(TAG, "only updating icons");
+ PrivacyChipBuilder builder = new PrivacyChipBuilder(mContext, mPrivacyItems);
+ setIcons(builder.generateIcons(), mIconsContainer);
+ mIconsContainer.requestLayout();
+ }
} else {
hideIndicatorIfNeeded();
}
@@ -191,12 +172,7 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void hideIndicatorIfNeeded() {
- // If STOPPED, NOT_SHOWN or DISAPPEARING - nothing else for us to do here.
- if (mState != STATE_SHOWN && mState != STATE_APPEARING) return;
-
- if (hasActiveRecorders()) {
- return;
- }
+ if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) return;
if (mViewAndWindowAdded) {
mState = STATE_DISAPPEARING;
@@ -210,23 +186,12 @@ public class AudioRecordingDisclosureBar implements
}
@UiThread
- private void showIfNeeded() {
- // If STOPPED, SHOWN or APPEARING - nothing else for us to do here.
- if (mState != STATE_NOT_SHOWN && mState != STATE_DISAPPEARING) return;
-
- if (DEBUG) Log.d(TAG, "Showing indicator");
-
- final int prevState = mState;
+ private void showIndicator() {
mState = STATE_APPEARING;
- if (prevState == STATE_DISAPPEARING) {
- animateAppearance();
- return;
- }
-
// Inflate the indicator view
mIndicatorView = LayoutInflater.from(mContext).inflate(
- R.layout.tv_audio_recording_indicator, null);
+ R.layout.tv_ongoing_privacy_chip, null);
// 1. Set alpha to 0.
// 2. Wait until the window is shown and the view is laid out.
@@ -239,7 +204,7 @@ public class AudioRecordingDisclosureBar implements
@Override
public void onGlobalLayout() {
// State could have changed to NOT_SHOWN (if all the recorders are
- // already gone) to STOPPED (if the indicator was disabled)
+ // already gone)
if (mState != STATE_APPEARING) return;
mViewAndWindowAdded = true;
@@ -251,22 +216,41 @@ public class AudioRecordingDisclosureBar implements
}
});
- final boolean isLtr = mContext.getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR;
+ mIconsContainer = mIndicatorView.findViewById(R.id.icons_container);
+ PrivacyChipBuilder builder = new PrivacyChipBuilder(mContext, mPrivacyItems);
+ setIcons(builder.generateIcons(), mIconsContainer);
+
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WRAP_CONTENT,
WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
- layoutParams.gravity = Gravity.TOP | (isLtr ? Gravity.RIGHT : Gravity.LEFT);
+ layoutParams.gravity = Gravity.TOP | Gravity.END;
layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
layoutParams.packageName = mContext.getPackageName();
- final WindowManager windowManager = (WindowManager) mContext.getSystemService(
- Context.WINDOW_SERVICE);
+ final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.addView(mIndicatorView, layoutParams);
+
}
+ private void setIcons(List icons, ViewGroup iconsContainer) {
+ iconsContainer.removeAllViews();
+ for (int i = 0; i < icons.size(); i++) {
+ Drawable icon = icons.get(i);
+ icon.mutate().setTint(Color.WHITE);
+ ImageView imageView = new ImageView(mContext);
+ imageView.setImageDrawable(icon);
+ imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ mIconsContainer.addView(imageView, mIconSize, mIconSize);
+ if (i != 0) {
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) imageView.getLayoutParams();
+ layoutParams.setMarginStart(mIconMarginStart);
+ imageView.setLayoutParams(layoutParams);
+ }
+ }
+ }
private void animateAppearance() {
animateAlphaTo(1f);
@@ -333,22 +317,13 @@ public class AudioRecordingDisclosureBar implements
}
}
- private boolean hasActiveRecorders() {
- for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
- for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
- if (mExemptPackages.contains(activePackage)) continue;
- return true;
- }
- }
- return false;
- }
-
private void removeIndicatorView() {
if (DEBUG) Log.d(TAG, "removeIndicatorView");
- final WindowManager windowManager = (WindowManager) mContext.getSystemService(
- Context.WINDOW_SERVICE);
- windowManager.removeView(mIndicatorView);
+ final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ if (windowManager != null) {
+ windowManager.removeView(mIndicatorView);
+ }
mIndicatorView = null;
mAnimator = null;
@@ -356,26 +331,4 @@ public class AudioRecordingDisclosureBar implements
mViewAndWindowAdded = false;
}
- private static List splitByComma(String string) {
- return TextUtils.isEmpty(string) ? Collections.emptyList() : Arrays.asList(
- string.split(","));
- }
-
- private final DeviceConfig.OnPropertiesChangedListener mConfigChangeListener =
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- reloadExemptPackages();
-
- // Check if was enabled/disabled
- if (mIsEnabled != properties.getBoolean(ENABLED_FLAG, true)) {
- mIsEnabled = !mIsEnabled;
- if (mIsEnabled) {
- start();
- } else {
- stop();
- }
- }
- }
- };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index bdf2b0c24ba54..37a763b740e16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -22,12 +22,10 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar;
import javax.inject.Inject;
@@ -36,11 +34,6 @@ import dagger.Lazy;
/**
* Status bar implementation for "large screen" products that mostly present no on-screen nav.
* Serves as a collection of UI components, rather than showing its own UI.
- * The following is the list of elements that constitute the TV-specific status bar:
- *
- * - {@link AudioRecordingDisclosureBar} - shown whenever applications are conducting audio
- * recording, discloses the responsible applications
- *
*/
@SysUISingleton
public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
@@ -66,11 +59,6 @@ public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
-
- if (mContext.getResources().getBoolean(R.bool.audio_recording_disclosure_enabled)) {
- // Creating AudioRecordingDisclosureBar and just letting it run
- new AudioRecordingDisclosureBar(mContext);
- }
}
@Override
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
deleted file mode 100644
index bbab6253a4d18..0000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioActivityObserver.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.tv.micdisclosure;
-
-import android.content.Context;
-
-import java.util.Set;
-
-/**
- * A base class for implementing observers for different kinds of activities related to audio
- * recording. These observers are to be initialized by {@link AudioRecordingDisclosureBar} and to
- * report back to it.
- */
-abstract class AudioActivityObserver {
-
- interface OnAudioActivityStateChangeListener {
- void onAudioActivityStateChange(boolean active, String packageName);
- }
-
- final Context mContext;
-
- final OnAudioActivityStateChangeListener mListener;
-
- AudioActivityObserver(Context context, OnAudioActivityStateChangeListener listener) {
- mContext = context;
- mListener = listener;
- }
-
- abstract void start();
-
- abstract void stop();
-
- abstract Set getActivePackages();
-}
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
deleted file mode 100644
index 8caf95fb48f5e..0000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/MicrophoneForegroundServicesObserver.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.tv.micdisclosure;
-
-import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
-
-import static com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar.DEBUG;
-
-import android.annotation.UiThread;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.app.IProcessObserver;
-import android.content.Context;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * The purpose of these class is to detect packages that are running foreground services of type
- * 'microphone' and to report back to {@link AudioRecordingDisclosureBar}.
- */
-class MicrophoneForegroundServicesObserver extends AudioActivityObserver {
- private static final String TAG = "MicrophoneForegroundServicesObserver";
-
- 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).
- */
- private final SparseArray mPidToPackages = new SparseArray<>();
- /**
- * A dictionary that maps "active" packages to the number of the "active" processes associated
- * with those packages. We really only need this in case when one application is running in
- * multiple processes, so that we don't lose track of the package when one of its "active"
- * processes ceases, while others remain "active".
- */
- private final Map mPackageToProcessCount = new ArrayMap<>();
-
- MicrophoneForegroundServicesObserver(Context context,
- OnAudioActivityStateChangeListener listener) {
- super(context, listener);
- }
-
- @Override
- void start() {
- mActivityManager = ActivityManager.getService();
- try {
- mActivityManager.registerProcessObserver(mProcessObserver);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't register process observer", e);
- }
- }
-
- @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 mPackageToProcessCount.keySet();
- }
-
- @UiThread
- private void onProcessForegroundServicesChanged(int pid, boolean hasMicFgs) {
- final String[] changedPackages;
- if (hasMicFgs) {
- if (mPidToPackages.contains(pid)) {
- // We are already tracking this pid - ignore.
- changedPackages = null;
- } else {
- changedPackages = getPackageNames(pid);
- mPidToPackages.append(pid, changedPackages);
- }
- } else {
- changedPackages = mPidToPackages.removeReturnOld(pid);
- }
-
- if (changedPackages == null) {
- return;
- }
-
- for (int index = changedPackages.length - 1; index >= 0; index--) {
- final String packageName = changedPackages[index];
- int processCount = mPackageToProcessCount.getOrDefault(packageName, 0);
- final boolean shouldNotify;
- if (hasMicFgs) {
- processCount++;
- shouldNotify = processCount == 1;
- } else {
- processCount--;
- shouldNotify = processCount == 0;
- }
- if (processCount > 0) {
- mPackageToProcessCount.put(packageName, processCount);
- } else {
- mPackageToProcessCount.remove(packageName);
- }
- if (shouldNotify) notifyPackageStateChanged(packageName, hasMicFgs);
- }
- }
-
- @UiThread
- private void onProcessDied(int pid) {
- final String[] packages = mPidToPackages.removeReturnOld(pid);
- if (packages == null) {
- // This PID was not active - ignore.
- return;
- }
-
- for (int index = packages.length - 1; index >= 0; index--) {
- final String packageName = packages[index];
- int processCount = mPackageToProcessCount.getOrDefault(packageName, 0);
- if (processCount <= 0) {
- Log.e(TAG, "Bookkeeping error, process count for " + packageName + " is "
- + processCount);
- continue;
- }
- processCount--;
- if (processCount > 0) {
- mPackageToProcessCount.put(packageName, processCount);
- } else {
- mPackageToProcessCount.remove(packageName);
- notifyPackageStateChanged(packageName, false);
- }
- }
- }
-
- @UiThread
- private void notifyPackageStateChanged(String packageName, boolean active) {
- if (DEBUG) {
- Log.d(TAG, (active ? "New microphone fgs detected" : "Microphone fgs is gone")
- + ", package=" + packageName);
- }
-
- mListener.onAudioActivityStateChange(active, packageName);
- }
-
- @UiThread
- private String[] getPackageNames(int pid) {
- final List runningApps;
- try {
- runningApps = mActivityManager.getRunningAppProcesses();
- } catch (RemoteException e) {
- Log.d(TAG, "Couldn't get package name for pid=" + pid);
- return null;
- }
- if (runningApps == null) {
- Log.wtf(TAG, "No running apps reported");
- }
- for (ActivityManager.RunningAppProcessInfo app : runningApps) {
- if (app.pid == pid) {
- return app.pkgList;
- }
- }
- return null;
- }
-
- private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
- @Override
- public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {}
-
- @Override
- public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
- mContext.getMainExecutor().execute(() -> onProcessForegroundServicesChanged(pid,
- (serviceTypes & FOREGROUND_SERVICE_TYPE_MICROPHONE) != 0));
- }
-
- @Override
- public void onProcessDied(int pid, int uid) {
- mContext.getMainExecutor().execute(
- () -> MicrophoneForegroundServicesObserver.this.onProcessDied(pid));
- }
- };
-}
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
deleted file mode 100644
index 9a2b4a93ac898..0000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/RecordAudioAppOpObserver.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.tv.micdisclosure;
-
-import static com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar.DEBUG;
-
-import android.annotation.UiThread;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.util.ArraySet;
-import android.util.Log;
-
-import java.util.Set;
-
-/**
- * The purpose of these class is to detect packages that are conducting audio recording (according
- * to {@link AppOpsManager}) and report this to {@link AudioRecordingDisclosureBar}.
- */
-class RecordAudioAppOpObserver extends AudioActivityObserver implements
- AppOpsManager.OnOpActiveChangedListener {
- private static final String TAG = "RecordAudioAppOpObserver";
-
- /**
- * Set of the applications that currently are conducting audio recording according to {@link
- * AppOpsManager}.
- */
- private final Set mActiveAudioRecordingPackages = new ArraySet<>();
-
- RecordAudioAppOpObserver(Context context, OnAudioActivityStateChangeListener listener) {
- super(context, listener);
- }
-
- @Override
- void start() {
- if (DEBUG) {
- Log.d(TAG, "Start");
- }
-
- // Register AppOpsManager callback
- 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
- @Override
- Set getActivePackages() {
- return mActiveAudioRecordingPackages;
- }
-
- @UiThread
- @Override
- public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
- if (DEBUG) {
- Log.d(TAG,
- "OP_RECORD_AUDIO active change, active=" + active + ", package="
- + packageName);
- }
-
- if (active) {
- if (mActiveAudioRecordingPackages.add(packageName)) {
- mListener.onAudioActivityStateChange(true, packageName);
- }
- } else {
- if (mActiveAudioRecordingPackages.remove(packageName)) {
- mListener.onAudioActivityStateChange(false, packageName);
- }
- }
- }
-}