Recording activity notification: client uid and package name
Add support for a system component to listen to recording activity
and know the uid and package name of the client app performing
the recording. This information is discarded for non-system
listeners on the server side.
Add log friendly dump for RecordActivityMonitor, AudioFormat and
audio source to dump recording activity in AudioService.
Test: run cts -m CtsMediaTestCases -t android.media.cts.AudioRecordingConfigurationTest#testAudioManagerGetActiveRecordConfigurations
Test: during recording, run "adb shell dumpsys audio", check output under RecordActivityMonitor
Bug 62579636
Change-Id: I60a223da3a2b7f7080bd7346fe3edc1df039466a
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 57d55de..34309b6 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -16,8 +16,11 @@
package com.android.server.audio;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioSystem;
import android.media.IRecordingConfigDispatcher;
@@ -26,7 +29,10 @@
import android.os.RemoteException;
import android.util.Log;
+import java.io.PrintWriter;
+import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -39,31 +45,47 @@
public final static String TAG = "AudioService.RecordingActivityMonitor";
private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
+ // a public client is one that needs an anonymized version of the playback configurations, we
+ // keep track of whether there is at least one to know when we need to create the list of
+ // playback configurations that do not contain uid/package name information.
+ private boolean mHasPublicClients = false;
private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs =
new HashMap<Integer, AudioRecordingConfiguration>();
- RecordingActivityMonitor() {
+ private final PackageManager mPackMan;
+
+ RecordingActivityMonitor(Context ctxt) {
RecMonitorClient.sMonitor = this;
+ mPackMan = ctxt.getPackageManager();
}
/**
* Implementation of android.media.AudioSystem.AudioRecordingCallback
*/
- public void onRecordingConfigurationChanged(int event, int session, int source,
- int[] recordingInfo) {
+ public void onRecordingConfigurationChanged(int event, int uid, int session, int source,
+ int[] recordingInfo, String packName) {
if (MediaRecorder.isSystemOnlyAudioSource(source)) {
return;
}
- final List<AudioRecordingConfiguration> configs =
- updateSnapshot(event, session, source, recordingInfo);
- if (configs != null){
- synchronized(mClients) {
+ final List<AudioRecordingConfiguration> configsSystem =
+ updateSnapshot(event, uid, session, source, recordingInfo);
+ if (configsSystem != null){
+ synchronized (mClients) {
+ // list of recording configurations for "public consumption". It is only computed if
+ // there are non-system recording activity listeners.
+ final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients ?
+ anonymizeForPublicConsumption(configsSystem) :
+ new ArrayList<AudioRecordingConfiguration>();
final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
while (clientIterator.hasNext()) {
+ final RecMonitorClient rmc = clientIterator.next();
try {
- clientIterator.next().mDispatcherCb.dispatchRecordingConfigChange(
- configs);
+ if (rmc.mIsPrivileged) {
+ rmc.mDispatcherCb.dispatchRecordingConfigChange(configsSystem);
+ } else {
+ rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
}
@@ -72,17 +94,42 @@
}
}
+ protected void dump(PrintWriter pw) {
+ // players
+ pw.println("\nRecordActivityMonitor dump time: "
+ + DateFormat.getTimeInstance().format(new Date()));
+ synchronized(mRecordConfigs) {
+ for (AudioRecordingConfiguration conf : mRecordConfigs.values()) {
+ conf.dump(pw);
+ }
+ }
+ }
+
+ private ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
+ List<AudioRecordingConfiguration> sysConfigs) {
+ ArrayList<AudioRecordingConfiguration> publicConfigs =
+ new ArrayList<AudioRecordingConfiguration>();
+ // only add active anonymized configurations,
+ for (AudioRecordingConfiguration config : sysConfigs) {
+ publicConfigs.add(AudioRecordingConfiguration.anonymizedCopy(config));
+ }
+ return publicConfigs;
+ }
+
void initMonitor() {
AudioSystem.setRecordingCallback(this);
}
- void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
+ void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
if (rcdb == null) {
return;
}
- synchronized(mClients) {
- final RecMonitorClient rmc = new RecMonitorClient(rcdb);
+ synchronized (mClients) {
+ final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
if (rmc.init()) {
+ if (!isPrivileged) {
+ mHasPublicClients = true;
+ }
mClients.add(rmc);
}
}
@@ -92,22 +139,34 @@
if (rcdb == null) {
return;
}
- synchronized(mClients) {
+ synchronized (mClients) {
final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
+ boolean hasPublicClients = false;
while (clientIterator.hasNext()) {
RecMonitorClient rmc = clientIterator.next();
if (rcdb.equals(rmc.mDispatcherCb)) {
rmc.release();
clientIterator.remove();
- break;
+ } else {
+ if (!rmc.mIsPrivileged) {
+ hasPublicClients = true;
+ }
}
}
+ mHasPublicClients = hasPublicClients;
}
}
- List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
+ List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
synchronized(mRecordConfigs) {
- return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
+ if (isPrivileged) {
+ return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
+ } else {
+ final List<AudioRecordingConfiguration> configsPublic =
+ anonymizeForPublicConsumption(
+ new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values()));
+ return configsPublic;
+ }
}
}
@@ -122,8 +181,8 @@
* @return null if the list of active recording sessions has not been modified, a list
* with the current active configurations otherwise.
*/
- private List<AudioRecordingConfiguration> updateSnapshot(int event, int session, int source,
- int[] recordingInfo) {
+ private List<AudioRecordingConfiguration> updateSnapshot(int event, int uid, int session,
+ int source, int[] recordingInfo) {
final boolean configChanged;
final ArrayList<AudioRecordingConfiguration> configs;
synchronized(mRecordConfigs) {
@@ -147,10 +206,19 @@
.build();
final int patchHandle = recordingInfo[6];
final Integer sessionKey = new Integer(session);
+
+ final String[] packages = mPackMan.getPackagesForUid(uid);
+ final String packageName;
+ if (packages != null && packages.length > 0) {
+ packageName = packages[0];
+ } else {
+ packageName = "";
+ }
+ final AudioRecordingConfiguration updatedConfig =
+ new AudioRecordingConfiguration(uid, session, source,
+ clientFormat, deviceFormat, patchHandle, packageName);
+
if (mRecordConfigs.containsKey(sessionKey)) {
- final AudioRecordingConfiguration updatedConfig =
- new AudioRecordingConfiguration(session, source,
- clientFormat, deviceFormat, patchHandle);
if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) {
configChanged = false;
} else {
@@ -160,9 +228,7 @@
configChanged = true;
}
} else {
- mRecordConfigs.put(sessionKey,
- new AudioRecordingConfiguration(session, source,
- clientFormat, deviceFormat, patchHandle));
+ mRecordConfigs.put(sessionKey, updatedConfig);
configChanged = true;
}
break;
@@ -189,9 +255,11 @@
static RecordingActivityMonitor sMonitor;
final IRecordingConfigDispatcher mDispatcherCb;
+ final boolean mIsPrivileged;
- RecMonitorClient(IRecordingConfigDispatcher rcdb) {
+ RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
mDispatcherCb = rcdb;
+ mIsPrivileged = isPrivileged;
}
public void binderDied() {