diff options
5 files changed, 154 insertions, 7 deletions
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java index f6f482dd0cd3..eaf86bb3936c 100644 --- a/media/java/android/media/MediaMetrics.java +++ b/media/java/android/media/MediaMetrics.java @@ -53,6 +53,7 @@ public class MediaMetrics { public static final String AUDIO_SERVICE = AUDIO + SEPARATOR + "service"; public static final String AUDIO_VOLUME = AUDIO + SEPARATOR + "volume"; public static final String AUDIO_VOLUME_EVENT = AUDIO_VOLUME + SEPARATOR + "event"; + public static final String AUDIO_MODE = AUDIO + SEPARATOR + "mode"; } /** @@ -140,6 +141,10 @@ public class MediaMetrics { public static final Key<String> REQUEST = createKey("request", String.class); + // For audio mode + public static final Key<String> REQUESTED_MODE = + createKey("requestedMode", String.class); // audio_mode + // For Bluetooth public static final Key<String> SCO_AUDIO_MODE = createKey("scoAudioMode", String.class); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5e908b26fafa..c4eca605206d 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -283,6 +283,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_HDMI_VOLUME_CHECK = 28; private static final int MSG_PLAYBACK_CONFIG_CHANGE = 29; private static final int MSG_BROADCAST_MICROPHONE_MUTE = 30; + private static final int MSG_CHECK_MODE_FOR_UID = 31; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) @@ -3679,12 +3680,14 @@ public class AudioService extends IAudioService.Stub private final IBinder mCb; // To be notified of client's death private final int mPid; private final int mUid; + private String mPackage; private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client - SetModeDeathHandler(IBinder cb, int pid, int uid) { + SetModeDeathHandler(IBinder cb, int pid, int uid, String caller) { mCb = cb; mPid = pid; mUid = uid; + mPackage = caller; } public void binderDied() { @@ -3722,6 +3725,10 @@ public class AudioService extends IAudioService.Stub public int getUid() { return mUid; } + + public String getPackage() { + return mPackage; + } } /** @see AudioManager#setMode(int) */ @@ -3803,6 +3810,9 @@ public class AudioService extends IAudioService.Stub hdlr = h; // Remove from client list so that it is re-inserted at top of list iter.remove(); + if (hdlr.getMode() == AudioSystem.MODE_IN_COMMUNICATION) { + mAudioHandler.removeEqualMessages(MSG_CHECK_MODE_FOR_UID, hdlr); + } try { hdlr.getBinder().unlinkToDeath(hdlr, 0); if (cb != hdlr.getBinder()) { @@ -3833,7 +3843,7 @@ public class AudioService extends IAudioService.Stub } } else { if (hdlr == null) { - hdlr = new SetModeDeathHandler(cb, pid, uid); + hdlr = new SetModeDeathHandler(cb, pid, uid, caller); } // Register for client death notification try { @@ -3880,6 +3890,7 @@ public class AudioService extends IAudioService.Stub // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL mModeLogger.log( new PhoneStateEvent(caller, pid, mode, newModeOwnerPid, actualMode)); + int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); int device = getDeviceForStream(streamType); int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device); @@ -3890,6 +3901,16 @@ public class AudioService extends IAudioService.Stub // change of mode may require volume to be re-applied on some devices updateAbsVolumeMultiModeDevices(oldMode, actualMode); + + if (actualMode == AudioSystem.MODE_IN_COMMUNICATION) { + sendMsg(mAudioHandler, + MSG_CHECK_MODE_FOR_UID, + SENDMSG_QUEUE, + 0, + 0, + hdlr, + CHECK_MODE_FOR_UID_PERIOD_MS); + } } return newModeOwnerPid; } @@ -6374,6 +6395,35 @@ public class AudioService extends IAudioService.Stub case MSG_BROADCAST_MICROPHONE_MUTE: mSystemServer.sendMicrophoneMuteChangedIntent(); break; + + case MSG_CHECK_MODE_FOR_UID: + synchronized (mDeviceBroker.mSetModeLock) { + if (msg.obj == null) { + break; + } + // If the app corresponding to this mode death handler object is not + // capturing or playing audio anymore after 3 seconds, remove it + // from the stack. Otherwise, check again in 3 seconds. + SetModeDeathHandler h = (SetModeDeathHandler) msg.obj; + if (mSetModeDeathHandlers.indexOf(h) < 0) { + break; + } + if (mRecordMonitor.isRecordingActiveForUid(h.getUid()) + || mPlaybackMonitor.isPlaybackActiveForUid(h.getUid())) { + sendMsg(mAudioHandler, + MSG_CHECK_MODE_FOR_UID, + SENDMSG_QUEUE, + 0, + 0, + h, + CHECK_MODE_FOR_UID_PERIOD_MS); + break; + } + // For now just log the fact that an app is hogging the audio mode. + // TODO(b/160260850): remove abusive app from audio mode stack. + mModeLogger.log(new PhoneStateEvent(h.getPackage(), h.getPid())); + } + break; } } } @@ -7017,6 +7067,8 @@ public class AudioService extends IAudioService.Stub private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed + // check playback or record activity every 3 seconds for UIDs owning mode IN_COMMUNICATION + private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 3000; private int safeMediaVolumeIndex(int device) { if (!mSafeMediaVolumeDevices.contains(device)) { diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index f3ff02f3aedc..0eb5a5d1fb48 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -27,28 +27,82 @@ import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState; public class AudioServiceEvents { final static class PhoneStateEvent extends AudioEventLogger.Event { + static final int MODE_SET = 0; + static final int MODE_IN_COMMUNICATION_TIMEOUT = 1; + + final int mOp; final String mPackage; final int mOwnerPid; final int mRequesterPid; final int mRequestedMode; final int mActualMode; + /** used for MODE_SET */ PhoneStateEvent(String callingPackage, int requesterPid, int requestedMode, int ownerPid, int actualMode) { + mOp = MODE_SET; mPackage = callingPackage; mRequesterPid = requesterPid; mRequestedMode = requestedMode; mOwnerPid = ownerPid; mActualMode = actualMode; + logMetricEvent(); + } + + /** used for MODE_IN_COMMUNICATION_TIMEOUT */ + PhoneStateEvent(String callingPackage, int ownerPid) { + mOp = MODE_IN_COMMUNICATION_TIMEOUT; + mPackage = callingPackage; + mOwnerPid = ownerPid; + mRequesterPid = 0; + mRequestedMode = 0; + mActualMode = 0; + logMetricEvent(); } @Override public String eventToString() { - return new StringBuilder("setMode(").append(AudioSystem.modeToString(mRequestedMode)) - .append(") from package=").append(mPackage) - .append(" pid=").append(mRequesterPid) - .append(" selected mode=").append(AudioSystem.modeToString(mActualMode)) - .append(" by pid=").append(mOwnerPid).toString(); + switch (mOp) { + case MODE_SET: + return new StringBuilder("setMode(") + .append(AudioSystem.modeToString(mRequestedMode)) + .append(") from package=").append(mPackage) + .append(" pid=").append(mRequesterPid) + .append(" selected mode=") + .append(AudioSystem.modeToString(mActualMode)) + .append(" by pid=").append(mOwnerPid).toString(); + case MODE_IN_COMMUNICATION_TIMEOUT: + return new StringBuilder("mode IN COMMUNICATION timeout") + .append(" for package=").append(mPackage) + .append(" pid=").append(mOwnerPid).toString(); + default: return new StringBuilder("FIXME invalid op:").append(mOp).toString(); + } + } + + /** + * Audio Analytics unique Id. + */ + private static final String mMetricsId = MediaMetrics.Name.AUDIO_MODE; + + private void logMetricEvent() { + switch (mOp) { + case MODE_SET: + new MediaMetrics.Item(mMetricsId) + .set(MediaMetrics.Property.EVENT, "set") + .set(MediaMetrics.Property.REQUESTED_MODE, + AudioSystem.modeToString(mRequestedMode)) + .set(MediaMetrics.Property.MODE, AudioSystem.modeToString(mActualMode)) + .set(MediaMetrics.Property.CALLING_PACKAGE, mPackage) + .record(); + return; + case MODE_IN_COMMUNICATION_TIMEOUT: + new MediaMetrics.Item(mMetricsId) + .set(MediaMetrics.Property.EVENT, "inCommunicationTimeout") + .set(MediaMetrics.Property.CALLING_PACKAGE, mPackage) + .record(); + return; + default: return; + } } } diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 98f409ea98e7..a5778836aa6e 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -366,6 +366,23 @@ public final class PlaybackActivityMonitor releasePlayer(piid, 0); } + /** + * Returns true if a player belonging to the app with given uid is active. + * + * @param uid the app uid + * @return true if a player is active, false otherwise + */ + public boolean isPlaybackActiveForUid(int uid) { + synchronized (mPlayerLock) { + for (AudioPlaybackConfiguration apc : mPlayers.values()) { + if (apc.isActive() && apc.getClientUid() == uid) { + return true; + } + } + } + return false; + } + protected void dump(PrintWriter pw) { // players pw.println("\nPlaybackActivityMonitor dump time: " diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 32c6cc32a78d..ea0107ecfd23 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -215,6 +215,25 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin dispatchCallbacks(updateSnapshot(AudioManager.RECORD_CONFIG_EVENT_RELEASE, riid, null)); } + /** + * Returns true if a recorder belonging to the app with given uid is active. + * + * @param uid the app uid + * @return true if a recorder is active, false otherwise + */ + public boolean isRecordingActiveForUid(int uid) { + synchronized (mRecordStates) { + for (RecordingState state : mRecordStates) { + // Note: isActiveConfiguration() == true => state.getConfig() != null + if (state.isActiveConfiguration() + && state.getConfig().getClientUid() == uid) { + return true; + } + } + } + return false; + } + private void dispatchCallbacks(List<AudioRecordingConfiguration> configs) { if (configs == null) { // null means "no changes" return; |