diff options
| -rw-r--r-- | core/jni/android_media_AudioSystem.cpp | 10 | ||||
| -rw-r--r-- | media/java/android/media/AudioFormat.java | 42 | ||||
| -rw-r--r-- | media/java/android/media/AudioRecordingConfiguration.java | 81 | ||||
| -rw-r--r-- | media/java/android/media/AudioSystem.java | 15 | ||||
| -rw-r--r-- | media/java/android/media/MediaRecorder.java | 34 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/AudioService.java | 16 | ||||
| -rw-r--r-- | services/core/java/com/android/server/audio/RecordingActivityMonitor.java | 118 |
7 files changed, 273 insertions, 43 deletions
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 1779ada24aac..e8f6074051cf 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -396,7 +396,7 @@ android_media_AudioSystem_dyn_policy_callback(int event, String8 regId, int val) } static void -android_media_AudioSystem_recording_callback(int event, audio_session_t session, int source, +android_media_AudioSystem_recording_callback(int event, const record_client_info_t *clientInfo, const audio_config_base_t *clientConfig, const audio_config_base_t *deviceConfig, audio_patch_handle_t patchHandle) { @@ -404,8 +404,8 @@ android_media_AudioSystem_recording_callback(int event, audio_session_t session, if (env == NULL) { return; } - if (clientConfig == NULL || deviceConfig == NULL) { - ALOGE("Unexpected null client/device configurations in recording callback"); + if (clientInfo == NULL || clientConfig == NULL || deviceConfig == NULL) { + ALOGE("Unexpected null client/device info or configurations in recording callback"); return; } @@ -433,7 +433,7 @@ android_media_AudioSystem_recording_callback(int event, audio_session_t session, jclass clazz = env->FindClass(kClassPathName); env->CallStaticVoidMethod(clazz, gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative, - event, session, source, recParamArray); + event, (jint) clientInfo->uid, clientInfo->session, clientInfo->source, recParamArray); env->DeleteLocalRef(clazz); env->DeleteLocalRef(recParamArray); @@ -1930,7 +1930,7 @@ int register_android_media_AudioSystem(JNIEnv *env) "dynamicPolicyCallbackFromNative", "(ILjava/lang/String;I)V"); gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative = GetStaticMethodIDOrDie(env, env->FindClass(kClassPathName), - "recordingCallbackFromNative", "(III[I)V"); + "recordingCallbackFromNative", "(IIII[I)V"); jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix"); gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass); diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index 81cc93da2e6f..93fc3da54550 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -267,6 +267,42 @@ public final class AudioFormat implements Parcelable { **/ public static final int ENCODING_DOLBY_TRUEHD = 14; + /** @hide */ + public static String toLogFriendlyEncoding(int enc) { + switch(enc) { + case ENCODING_INVALID: + return "ENCODING_INVALID"; + case ENCODING_PCM_16BIT: + return "ENCODING_PCM_16BIT"; + case ENCODING_PCM_8BIT: + return "ENCODING_PCM_8BIT"; + case ENCODING_PCM_FLOAT: + return "ENCODING_PCM_FLOAT"; + case ENCODING_AC3: + return "ENCODING_AC3"; + case ENCODING_E_AC3: + return "ENCODING_E_AC3"; + case ENCODING_DTS: + return "ENCODING_DTS"; + case ENCODING_DTS_HD: + return "ENCODING_DTS_HD"; + case ENCODING_MP3: + return "ENCODING_MP3"; + case ENCODING_AAC_LC: + return "ENCODING_AAC_LC"; + case ENCODING_AAC_HE_V1: + return "ENCODING_AAC_HE_V1"; + case ENCODING_AAC_HE_V2: + return "ENCODING_AAC_HE_V2"; + case ENCODING_IEC61937: + return "ENCODING_IEC61937"; + case ENCODING_DOLBY_TRUEHD: + return "ENCODING_DOLBY_TRUEHD"; + default : + return "invalid encoding " + enc; + } + } + /** Invalid audio channel configuration */ /** @deprecated Use {@link #CHANNEL_INVALID} instead. */ @Deprecated public static final int CHANNEL_CONFIGURATION_INVALID = 0; @@ -693,6 +729,12 @@ public final class AudioFormat implements Parcelable { return mPropertySetMask; } + /** @hide */ + public String toLogFriendlyString() { + return String.format("%dch %dHz %s", + getChannelCount(), mSampleRate, toLogFriendlyEncoding(mEncoding)); + } + /** * Builder class for {@link AudioFormat} objects. * Use this class to configure and create an AudioFormat instance. By setting format diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java index 50dbd035b84d..984c5542cb7b 100644 --- a/media/java/android/media/AudioRecordingConfiguration.java +++ b/media/java/android/media/AudioRecordingConfiguration.java @@ -17,10 +17,12 @@ package android.media; import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -52,18 +54,59 @@ public final class AudioRecordingConfiguration implements Parcelable { private final AudioFormat mDeviceFormat; private final AudioFormat mClientFormat; + @NonNull private final String mClientPackageName; + private final int mClientUid; + private final int mPatchHandle; /** * @hide */ - public AudioRecordingConfiguration(int session, int source, AudioFormat clientFormat, - AudioFormat devFormat, int patchHandle) { + public AudioRecordingConfiguration(int uid, int session, int source, AudioFormat clientFormat, + AudioFormat devFormat, int patchHandle, String packageName) { + mClientUid = uid; mSessionId = session; mClientSource = source; mClientFormat = clientFormat; mDeviceFormat = devFormat; mPatchHandle = patchHandle; + mClientPackageName = packageName; + } + + /** + * @hide + * For AudioService dump + * @param pw + */ + public void dump(PrintWriter pw) { + pw.println(" " + toLogFriendlyString(this)); + } + + /** + * @hide + */ + public static String toLogFriendlyString(AudioRecordingConfiguration arc) { + return new String("session:" + arc.mSessionId + + " -- source:" + MediaRecorder.toLogFriendlyAudioSource(arc.mClientSource) + + " -- uid:" + arc.mClientUid + + " -- patch:" + arc.mPatchHandle + + " -- pack:" + arc.mClientPackageName + + " -- format client=" + arc.mClientFormat.toLogFriendlyString() + + ", dev=" + arc.mDeviceFormat.toLogFriendlyString()); + } + + // Note that this method is called server side, so no "privileged" information is ever sent + // to a client that is not supposed to have access to it. + /** + * @hide + * Creates a copy of the recording configuration that is stripped of any data enabling + * identification of which application it is associated with ("anonymized"). + * @param in + */ + public static AudioRecordingConfiguration anonymizedCopy(AudioRecordingConfiguration in) { + return new AudioRecordingConfiguration( /*anonymized uid*/ -1, + in.mSessionId, in.mClientSource, in.mClientFormat, + in.mDeviceFormat, in.mPatchHandle, "" /*empty package name*/); } // matches the sources that return false in MediaRecorder.isSystemOnlyAudioSource(source) @@ -120,6 +163,30 @@ public final class AudioRecordingConfiguration implements Parcelable { public AudioFormat getClientFormat() { return mClientFormat; } /** + * @pending for SystemApi + * Returns the package name of the application performing the recording. + * Where there are multiple packages sharing the same user id through the "sharedUserId" + * mechanism, only the first one with that id will be returned + * (see {@link PackageManager#getPackagesForUid(int)}). + * <p>This information is only available if the caller has the + * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING} permission. + * <br>When called without the permission, the result is an empty string. + * @return the package name + */ + public String getClientPackageName() { return mClientPackageName; } + + /** + * @pending for SystemApi + * Returns the user id of the application performing the recording. + * <p>This information is only available if the caller has the + * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING} + * permission. + * <br>The result is -1 without the permission. + * @return the user id + */ + public int getClientUid() { return mClientUid; } + + /** * Returns information about the audio input device used for this recording. * @return the audio recording device or null if this information cannot be retrieved */ @@ -185,6 +252,8 @@ public final class AudioRecordingConfiguration implements Parcelable { mClientFormat.writeToParcel(dest, 0); mDeviceFormat.writeToParcel(dest, 0); dest.writeInt(mPatchHandle); + dest.writeString(mClientPackageName); + dest.writeInt(mClientUid); } private AudioRecordingConfiguration(Parcel in) { @@ -193,6 +262,8 @@ public final class AudioRecordingConfiguration implements Parcelable { mClientFormat = AudioFormat.CREATOR.createFromParcel(in); mDeviceFormat = AudioFormat.CREATOR.createFromParcel(in); mPatchHandle = in.readInt(); + mClientPackageName = in.readString(); + mClientUid = in.readInt(); } @Override @@ -202,10 +273,12 @@ public final class AudioRecordingConfiguration implements Parcelable { AudioRecordingConfiguration that = (AudioRecordingConfiguration) o; - return ((mSessionId == that.mSessionId) + return ((mClientUid == that.mClientUid) + && (mSessionId == that.mSessionId) && (mClientSource == that.mClientSource) && (mPatchHandle == that.mPatchHandle) && (mClientFormat.equals(that.mClientFormat)) - && (mDeviceFormat.equals(that.mDeviceFormat))); + && (mDeviceFormat.equals(that.mDeviceFormat)) + && (mClientPackageName.equals(that.mClientPackageName))); } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 6ef3091dcc70..c7c2dd8aedcd 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -287,6 +287,7 @@ public class AudioSystem /** * Callback for recording activity notifications events * @param event + * @param uid uid of the client app performing the recording * @param session * @param source * @param recordingFormat an array of ints containing respectively the client and device @@ -298,9 +299,10 @@ public class AudioSystem * 4: device channel mask * 5: device sample rate * 6: patch handle + * @param packName package name of the client app performing the recording. NOT SUPPORTED */ - void onRecordingConfigurationChanged(int event, int session, int source, - int[] recordingFormat); + void onRecordingConfigurationChanged(int event, int uid, int session, int source, + int[] recordingFormat, String packName); } private static AudioRecordingCallback sRecordingCallback; @@ -318,17 +320,18 @@ public class AudioSystem * @param session * @param source * @param recordingFormat see - * {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])} for - * the description of the record format. + * {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int, int[])} + * for the description of the record format. */ - private static void recordingCallbackFromNative(int event, int session, int source, + private static void recordingCallbackFromNative(int event, int uid, int session, int source, int[] recordingFormat) { AudioRecordingCallback cb = null; synchronized (AudioSystem.class) { cb = sRecordingCallback; } if (cb != null) { - cb.onRecordingConfigurationChanged(event, session, source, recordingFormat); + // TODO receive package name from native + cb.onRecordingConfigurationChanged(event, uid, session, source, recordingFormat, ""); } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 33a7c836dec8..59a124fa434f 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -324,6 +324,40 @@ public class MediaRecorder } } + /** @hide */ + public static final String toLogFriendlyAudioSource(int source) { + switch(source) { + case AudioSource.DEFAULT: + return "DEFAULT"; + case AudioSource.MIC: + return "MIC"; + case AudioSource.VOICE_UPLINK: + return "VOICE_UPLINK"; + case AudioSource.VOICE_DOWNLINK: + return "VOICE_DOWNLINK"; + case AudioSource.VOICE_CALL: + return "VOICE_CALL"; + case AudioSource.CAMCORDER: + return "CAMCORDER"; + case AudioSource.VOICE_RECOGNITION: + return "VOICE_RECOGNITION"; + case AudioSource.VOICE_COMMUNICATION: + return "VOICE_COMMUNICATION"; + case AudioSource.REMOTE_SUBMIX: + return "REMOTE_SUBMIX"; + case AudioSource.UNPROCESSED: + return "UNPROCESSED"; + case AudioSource.RADIO_TUNER: + return "RADIO_TUNER"; + case AudioSource.HOTWORD: + return "HOTWORD"; + case AudioSource.AUDIO_SOURCE_INVALID: + return "AUDIO_SOURCE_INVALID"; + default: + return "unknown source " + source; + } + } + /** * Defines the video source. These constants are used with * {@link MediaRecorder#setVideoSource(int)}. diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index e5ab784df882..c6307a793e34 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -706,6 +706,8 @@ public class AudioService extends IAudioService.Stub mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor); + mRecordMonitor = new RecordingActivityMonitor(mContext); + readAndSetLowRamDevice(); // Call setRingerModeInt() to apply correct mute @@ -6309,6 +6311,8 @@ public class AudioService extends IAudioService.Stub dumpAudioPolicies(pw); mPlaybackMonitor.dump(pw); + + mRecordMonitor.dump(pw); } private static String safeMediaVolumeStateToString(Integer state) { @@ -6730,10 +6734,13 @@ public class AudioService extends IAudioService.Stub //====================== // Audio policy callbacks from AudioSystem for recording configuration updates //====================== - private final RecordingActivityMonitor mRecordMonitor = new RecordingActivityMonitor(); + private final RecordingActivityMonitor mRecordMonitor; public void registerRecordingCallback(IRecordingConfigDispatcher rcdb) { - mRecordMonitor.registerRecordingCallback(rcdb); + final boolean isPrivileged = + (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + mRecordMonitor.registerRecordingCallback(rcdb, isPrivileged); } public void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) { @@ -6741,7 +6748,10 @@ public class AudioService extends IAudioService.Stub } public List<AudioRecordingConfiguration> getActiveRecordingConfigurations() { - return mRecordMonitor.getActiveRecordingConfigurations(); + final boolean isPrivileged = + (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged); } public void disableRingtoneSync(final int userId) { diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 57d55de8d002..34309b62d2f7 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.IBinder; 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 class RecordingActivityMonitor implements AudioSystem.AudioRecordin 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 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin } } + 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 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin 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 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin * @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 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin .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 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin configChanged = true; } } else { - mRecordConfigs.put(sessionKey, - new AudioRecordingConfiguration(session, source, - clientFormat, deviceFormat, patchHandle)); + mRecordConfigs.put(sessionKey, updatedConfig); configChanged = true; } break; @@ -189,9 +255,11 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin static RecordingActivityMonitor sMonitor; final IRecordingConfigDispatcher mDispatcherCb; + final boolean mIsPrivileged; - RecMonitorClient(IRecordingConfigDispatcher rcdb) { + RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) { mDispatcherCb = rcdb; + mIsPrivileged = isPrivileged; } public void binderDied() { |