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); - } - } - } -}