Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6915e2ff authored by Eric Laurent's avatar Eric Laurent
Browse files

AudioService: Add metrics for audio mode

A metrics for audio mode changes and failure to release IN_COMMUNICATION
mode when inactive.

Bug: 153934174
Bug: 161444687
Test: repro steps in Bug and verify logs and metrics
Change-Id: Ib3beab2e8b64e7354ff1a32f04561e7b95f61855
parent 1b09322b
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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);
+54 −2
Original line number Diff line number Diff line
@@ -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)) {
+59 −5
Original line number Diff line number Diff line
@@ -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))
            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(" 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;
            }
        }
    }

+17 −0
Original line number Diff line number Diff line
@@ -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: "
+19 −0
Original line number Diff line number Diff line
@@ -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;