Account for mic fgs in AudioRecordingDisclosureBar
Take into account foreground services of type 'microphone' in AudioRecordingDisclosureBar - watch processes that run such fg services and treat them as so they are already recording. Bug: 152364373 Test: make, run audio recording app Change-Id: I7961ad332d5741a12f029dcc95e909572f30ad05
This commit is contained in:
@@ -255,6 +255,9 @@
|
||||
<!-- Query all packages on device on R+ -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
|
||||
<!-- Permission to register process observer -->
|
||||
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
|
||||
|
||||
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
|
||||
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
|
||||
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.os.UserHandle;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.systemui.SystemUI;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -66,7 +67,8 @@ public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
|
||||
// If the system process isn't there we're doomed anyway.
|
||||
}
|
||||
|
||||
new AudioRecordingDisclosureBar(mContext).start();
|
||||
// Creating AudioRecordingDisclosureBar and just letting it run
|
||||
new AudioRecordingDisclosureBar(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 Set<String> getActivePackages();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.tv;
|
||||
package com.android.systemui.statusbar.tv.micdisclosure;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.UiThread;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -40,6 +39,7 @@ import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.tv.TvStatusBar;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -54,9 +54,10 @@ import java.util.Set;
|
||||
*
|
||||
* @see TvStatusBar
|
||||
*/
|
||||
class AudioRecordingDisclosureBar {
|
||||
private static final String TAG = "AudioRecordingDisclosureBar";
|
||||
private static final boolean DEBUG = false;
|
||||
public class AudioRecordingDisclosureBar implements
|
||||
AudioActivityObserver.OnAudioActivityStateChangeListener {
|
||||
private static final String TAG = "AudioRecordingDisclosure";
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
// This title is used to test the microphone disclosure indicator in
|
||||
// CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
|
||||
@@ -98,10 +99,12 @@ class AudioRecordingDisclosureBar {
|
||||
private TextView mTextView;
|
||||
|
||||
@State private int mState = STATE_NOT_SHOWN;
|
||||
|
||||
/**
|
||||
* Set of the applications that currently are conducting audio recording.
|
||||
* Array of the observers that monitor different aspects of the system, such as AppOps and
|
||||
* microphone foreground services
|
||||
*/
|
||||
private final Set<String> mActiveAudioRecordingPackages = new ArraySet<>();
|
||||
private final AudioActivityObserver[] mAudioActivityObservers;
|
||||
/**
|
||||
* Set of applications that we've notified the user about since the indicator came up. Meaning
|
||||
* that if an application is in this list then at some point since the indicator came up, it
|
||||
@@ -119,29 +122,52 @@ class AudioRecordingDisclosureBar {
|
||||
* one of the two aforementioned states.
|
||||
*/
|
||||
private final Queue<String> mPendingNotificationPackages = new LinkedList<>();
|
||||
/**
|
||||
* 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<String> mExemptPackages;
|
||||
|
||||
AudioRecordingDisclosureBar(Context context) {
|
||||
public AudioRecordingDisclosureBar(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
void start() {
|
||||
// Register AppOpsManager callback
|
||||
final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService(
|
||||
Context.APP_OPS_SERVICE);
|
||||
appOpsManager.startWatchingActive(
|
||||
new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
|
||||
mContext.getMainExecutor(),
|
||||
new OnActiveRecordingListener());
|
||||
mExemptPackages = new ArraySet<>(
|
||||
Arrays.asList(mContext.getResources().getStringArray(
|
||||
R.array.audio_recording_disclosure_exempt_apps)));
|
||||
|
||||
mAudioActivityObservers = new AudioActivityObserver[]{
|
||||
new RecordAudioAppOpObserver(mContext, this),
|
||||
new MicrophoneForegroundServicesObserver(mContext, this),
|
||||
};
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onStartedRecording(String packageName) {
|
||||
if (!mActiveAudioRecordingPackages.add(packageName)) {
|
||||
// This app is already known to perform recording
|
||||
@Override
|
||||
public void onAudioActivityStateChange(boolean active, String packageName) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG,
|
||||
"onAudioActivityStateChange, packageName=" + packageName + ", active="
|
||||
+ active);
|
||||
}
|
||||
|
||||
if (mExemptPackages.contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, " - exempt package: ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
showIndicatorForPackageIfNeeded(packageName);
|
||||
} else {
|
||||
hideIndicatorIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showIndicatorForPackageIfNeeded(String packageName) {
|
||||
if (DEBUG) Log.d(TAG, "showIndicatorForPackageIfNeeded, packageName=" + packageName);
|
||||
if (!mSessionNotifiedPackages.add(packageName)) {
|
||||
// We've already notified user about this app, no need to do it again.
|
||||
if (DEBUG) Log.d(TAG, " - already notified");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -167,23 +193,33 @@ class AudioRecordingDisclosureBar {
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onDoneRecording(String packageName) {
|
||||
if (!mActiveAudioRecordingPackages.remove(packageName)) {
|
||||
// Was not marked as an active recorder, do nothing
|
||||
return;
|
||||
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;
|
||||
|
||||
// If is in the STATE_MINIMIZED, but there are other active recorders - simply ignore.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// If not MINIMIZED, will check whether the indicator should be hidden when the indicator
|
||||
// comes to the STATE_MINIMIZED eventually. If is in the STATE_MINIMIZED, but there are
|
||||
// other active recorders - simply ignore.
|
||||
if (mState == STATE_MINIMIZED && mActiveAudioRecordingPackages.isEmpty()) {
|
||||
mSessionNotifiedPackages.clear();
|
||||
hide();
|
||||
}
|
||||
// Clear the state and hide the indicator.
|
||||
mSessionNotifiedPackages.clear();
|
||||
hide();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void show(String packageName) {
|
||||
final String label = getApplicationLabel(packageName);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Showing indicator for " + packageName + " (" + label + ")...");
|
||||
}
|
||||
|
||||
// Inflate the indicator view
|
||||
mIndicatorView = LayoutInflater.from(mContext).inflate(
|
||||
R.layout.tv_audio_recording_indicator,
|
||||
@@ -196,7 +232,6 @@ class AudioRecordingDisclosureBar {
|
||||
mBgRight = mIndicatorView.findViewById(R.id.bg_right);
|
||||
|
||||
// Set up the notification text
|
||||
final String label = getApplicationLabel(packageName);
|
||||
mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
|
||||
|
||||
// Initially change the visibility to INVISIBLE, wait until and receives the size and
|
||||
@@ -260,6 +295,9 @@ class AudioRecordingDisclosureBar {
|
||||
@UiThread
|
||||
private void expand(String packageName) {
|
||||
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();
|
||||
@@ -283,6 +321,7 @@ class AudioRecordingDisclosureBar {
|
||||
|
||||
@UiThread
|
||||
private void minimize() {
|
||||
if (DEBUG) Log.d(TAG, "Minimizing...");
|
||||
final int targetOffset = mTextsContainers.getWidth();
|
||||
final AnimatorSet set = new AnimatorSet();
|
||||
set.playTogether(
|
||||
@@ -305,6 +344,7 @@ class AudioRecordingDisclosureBar {
|
||||
|
||||
@UiThread
|
||||
private void hide() {
|
||||
if (DEBUG) Log.d(TAG, "Hiding...");
|
||||
final int targetOffset =
|
||||
mIndicatorView.getWidth() - (int) mIconTextsContainer.getTranslationX();
|
||||
final AnimatorSet set = new AnimatorSet();
|
||||
@@ -326,6 +366,7 @@ class AudioRecordingDisclosureBar {
|
||||
|
||||
@UiThread
|
||||
private void onExpanded() {
|
||||
if (DEBUG) Log.d(TAG, "Expanded");
|
||||
mState = STATE_SHOWN;
|
||||
|
||||
mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
|
||||
@@ -333,20 +374,21 @@ class AudioRecordingDisclosureBar {
|
||||
|
||||
@UiThread
|
||||
private void onMinimized() {
|
||||
if (DEBUG) Log.d(TAG, "Minimized");
|
||||
mState = STATE_MINIMIZED;
|
||||
|
||||
if (!mPendingNotificationPackages.isEmpty()) {
|
||||
// There is a new application that started recording, tell the user about it.
|
||||
expand(mPendingNotificationPackages.poll());
|
||||
} else if (mActiveAudioRecordingPackages.isEmpty()) {
|
||||
// Nobody is recording anymore, clear state and remove the indicator.
|
||||
mSessionNotifiedPackages.clear();
|
||||
hide();
|
||||
} else {
|
||||
hideIndicatorIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onHidden() {
|
||||
if (DEBUG) Log.d(TAG, "Hidden");
|
||||
|
||||
final WindowManager windowManager = (WindowManager) mContext.getSystemService(
|
||||
Context.WINDOW_SERVICE);
|
||||
windowManager.removeView(mIndicatorView);
|
||||
@@ -392,35 +434,4 @@ class AudioRecordingDisclosureBar {
|
||||
}
|
||||
return pm.getApplicationLabel(appInfo).toString();
|
||||
}
|
||||
|
||||
private class OnActiveRecordingListener implements AppOpsManager.OnOpActiveChangedListener {
|
||||
private final Set<String> mExemptApps;
|
||||
|
||||
private OnActiveRecordingListener() {
|
||||
mExemptApps = new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(
|
||||
R.array.audio_recording_disclosure_exempt_apps)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG,
|
||||
"OP_RECORD_AUDIO active change, active=" + active + ", app="
|
||||
+ packageName);
|
||||
}
|
||||
|
||||
if (mExemptApps.contains(packageName)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "\t- exempt app");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
onStartedRecording(packageName);
|
||||
} else {
|
||||
onDoneRecording(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
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 static final boolean ENABLED = true;
|
||||
|
||||
private final 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<String[]> 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<String, Integer> mPackageToProcessCount = new ArrayMap<>();
|
||||
|
||||
MicrophoneForegroundServicesObserver(Context context,
|
||||
OnAudioActivityStateChangeListener listener) {
|
||||
super(context, listener);
|
||||
|
||||
mActivityManager = ActivityManager.getService();
|
||||
try {
|
||||
mActivityManager.registerProcessObserver(mProcessObserver);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Couldn't register process observer", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<String> getActivePackages() {
|
||||
return ENABLED ? mPackageToProcessCount.keySet() : Collections.emptySet();
|
||||
}
|
||||
|
||||
@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 (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 (ENABLED) mListener.onAudioActivityStateChange(active, packageName);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private String[] getPackageNames(int pid) {
|
||||
final List<ActivityManager.RunningAppProcessInfo> 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));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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<String> mActiveAudioRecordingPackages = new ArraySet<>();
|
||||
|
||||
RecordAudioAppOpObserver(Context context, OnAudioActivityStateChangeListener listener) {
|
||||
super(context, listener);
|
||||
|
||||
// Register AppOpsManager callback
|
||||
final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService(
|
||||
Context.APP_OPS_SERVICE);
|
||||
appOpsManager.startWatchingActive(
|
||||
new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
|
||||
mContext.getMainExecutor(),
|
||||
this);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
Set<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user