blob: 1ede88a260207cbec159a5b42dc9eac504b3acde [file] [log] [blame]
/*
* 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));
}
};
}