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

Commit 385ca73d authored by Eric Laurent's avatar Eric Laurent
Browse files

AudioDeviceBroker: ignore communication route requests by idle apps

Monitor playback and recording activity for apps in the communication clients
stack. If the client is not the audio mode owner or not privileged,
do not take its routing request into account. The inactive client
remains in the stack.
Also track communication route clients by UID instead of PID to
facilitate matching with active players and recorders.

Bug: 286545833
Test: atest AudioCommunicationDeviceTest

Change-Id: I23f06b343efcc10c06a7eb482366c735da54da2d
parent a812ef3d
Loading
Loading
Loading
Loading
+138 −57
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.BluetoothProfileConnectionInfo;
@@ -289,37 +291,38 @@ import java.util.concurrent.atomic.AtomicBoolean;
     * @param on
     * @param eventSource for logging purposes
     */
    /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
    /*package*/ void setSpeakerphoneOn(
            IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) {

        if (AudioService.DEBUG_COMM_RTE) {
            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid);
        }
        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
    }

    /**
     * Select device for use for communication use cases.
     * @param cb Client binder for death detection
     * @param pid Client pid
     * @param uid Client uid
     * @param device Device selected or null to unselect.
     * @param eventSource for logging purposes
     */

    private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;

    /*package*/ boolean setCommunicationDevice(
            IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
    /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
                                               boolean isPrivileged, String eventSource) {

        if (AudioService.DEBUG_COMM_RTE) {
            Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
            Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
        }

        AudioDeviceAttributes deviceAttr =
                (device != null) ? new AudioDeviceAttributes(device) : null;
        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr,
                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true, isPrivileged);
        postSetCommunicationDeviceForClient(deviceInfo);
        boolean status;
        synchronized (deviceInfo) {
@@ -353,7 +356,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
            Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
        }
        if (!deviceInfo.mOn) {
            CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
            CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid);
            if (client == null || (deviceInfo.mDevice != null
                    && !deviceInfo.mDevice.equals(client.getDevice()))) {
                return false;
@@ -361,22 +364,22 @@ import java.util.concurrent.atomic.AtomicBoolean;
        }

        AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
                deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device,
                deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
        return true;
    }

    @GuardedBy("mDeviceStateLock")
    /*package*/ void setCommunicationRouteForClient(
                            IBinder cb, int pid, AudioDeviceAttributes device,
                            int scoAudioMode, String eventSource) {
                            IBinder cb, int uid, AudioDeviceAttributes device,
                            int scoAudioMode, boolean isPrivileged, String eventSource) {

        if (AudioService.DEBUG_COMM_RTE) {
            Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
        }
        AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                                        "setCommunicationRouteForClient for pid: " + pid
                                        + " device: " + device
                                        "setCommunicationRouteForClient for uid: " + uid
                                        + " device: " + device + " isPrivileged: " + isPrivileged
                                        + " from API: " + eventSource)).printLog(TAG));

        final boolean wasBtScoRequested = isBluetoothScoRequested();
@@ -385,16 +388,18 @@ import java.util.concurrent.atomic.AtomicBoolean;

        // Save previous client route in case of failure to start BT SCO audio
        AudioDeviceAttributes prevClientDevice = null;
        client = getCommunicationRouteClientForPid(pid);
        boolean prevPrivileged = false;
        client = getCommunicationRouteClientForUid(uid);
        if (client != null) {
            prevClientDevice = client.getDevice();
            prevPrivileged = client.isPrivileged();
        }

        if (device != null) {
            client = addCommunicationRouteClient(cb, pid, device);
            client = addCommunicationRouteClient(cb, uid, device, isPrivileged);
            if (client == null) {
                Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: "
                        + pid + " and device: " + device);
                Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: "
                        + uid + " and device: " + device);
            }
        } else {
            client = removeCommunicationRouteClient(cb, true);
@@ -406,11 +411,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
        boolean isBtScoRequested = isBluetoothScoRequested();
        if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
            if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
                        + pid);
                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
                        + uid);
                // clean up or restore previous client selection
                if (prevClientDevice != null) {
                    addCommunicationRouteClient(cb, pid, prevClientDevice);
                    addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
                } else {
                    removeCommunicationRouteClient(cb, true);
                }
@@ -447,11 +452,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
    @GuardedBy("mDeviceStateLock")
    private CommunicationRouteClient topCommunicationRouteClient() {
        for (CommunicationRouteClient crc : mCommunicationRouteClients) {
            if (crc.getPid() == mAudioModeOwner.mPid) {
            if (crc.getUid() == mAudioModeOwner.mUid) {
                return crc;
            }
        }
        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) {
        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0
                && mCommunicationRouteClients.get(0).isActive()) {
            return mCommunicationRouteClients.get(0);
        }
        return null;
@@ -1107,26 +1113,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
        sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
    }

    /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
                @NonNull String eventSource) {
    /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode,
                                                boolean isPrivileged, @NonNull String eventSource) {

        if (AudioService.DEBUG_COMM_RTE) {
            Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
            Log.v(TAG, "startBluetoothScoForClient, uid: " + uid);
        }
        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
                true, scoAudioMode, eventSource, false));
                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
                true, scoAudioMode, eventSource, false, isPrivileged));
    }

    /*package*/ void stopBluetoothScoForClient(
                        IBinder cb, int pid, @NonNull String eventSource) {
                        IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) {

        if (AudioService.DEBUG_COMM_RTE) {
            Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
            Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid);
        }
        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
    }

    /*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -1367,22 +1373,24 @@ import java.util.concurrent.atomic.AtomicBoolean;

    /*package*/ static final class CommunicationDeviceInfo {
        final @NonNull IBinder mCb; // Identifies the requesting client for death handler
        final int mPid; // Requester process ID
        final int mUid; // Requester UID
        final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
        final boolean mOn; // true if setting, false if resetting
        final int mScoAudioMode; // only used for SCO: requested audio mode
        final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission
        final @NonNull String mEventSource; // caller identifier for logging
        boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
        boolean mStatus = false; // completion status only used if mWaitForStatus is true

        CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
        CommunicationDeviceInfo(@NonNull IBinder cb, int uid,
                @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
                @NonNull String eventSource, boolean waitForStatus) {
                @NonNull String eventSource, boolean waitForStatus, boolean isPrivileged) {
            mCb = cb;
            mPid = pid;
            mUid = uid;
            mDevice = device;
            mOn = on;
            mScoAudioMode = scoAudioMode;
            mIsPrivileged = isPrivileged;
            mEventSource = eventSource;
            mWaitForStatus = waitForStatus;
        }
@@ -1401,16 +1409,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
            }

            return mCb.equals(((CommunicationDeviceInfo) o).mCb)
                    && mPid == ((CommunicationDeviceInfo) o).mPid;
                    && mUid == ((CommunicationDeviceInfo) o).mUid;
        }

        @Override
        public String toString() {
            return "CommunicationDeviceInfo mCb=" + mCb.toString()
                    + " mPid=" + mPid
                    + " mUid=" + mUid
                    + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
                    + " mOn=" + mOn
                    + " mScoAudioMode=" + mScoAudioMode
                    + " mIsPrivileged=" + mIsPrivileged
                    + " mEventSource=" + mEventSource
                    + " mWaitForStatus=" + mWaitForStatus
                    + " mStatus=" + mStatus;
@@ -1507,8 +1516,7 @@ import java.util.concurrent.atomic.AtomicBoolean;

        pw.println("\n" + prefix + "Communication route clients:");
        mCommunicationRouteClients.forEach((cl) -> {
            pw.println("  " + prefix + "pid: " + cl.getPid() + " device: "
                        + cl.getDevice() + " cb: " + cl.getBinder()); });
            pw.println("  " + prefix + cl.toString()); });

        pw.println("\n" + prefix + "Computed Preferred communication device: "
                +  preferredCommunicationDevice());
@@ -2101,13 +2109,20 @@ import java.util.concurrent.atomic.AtomicBoolean;

    private class CommunicationRouteClient implements IBinder.DeathRecipient {
        private final IBinder mCb;
        private final int mPid;
        private final int mUid;
        private final boolean mIsPrivileged;
        private AudioDeviceAttributes mDevice;
        private boolean mPlaybackActive;
        private boolean mRecordingActive;

        CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) {
        CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device,
                                 boolean isPrivileged) {
            mCb = cb;
            mPid = pid;
            mUid = uid;
            mDevice = device;
            mIsPrivileged = isPrivileged;
            mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid);
            mRecordingActive = mAudioService.isRecordingActiveForUid(uid);
        }

        public boolean registerDeathRecipient() {
@@ -2138,13 +2153,38 @@ import java.util.concurrent.atomic.AtomicBoolean;
            return mCb;
        }

        int getPid() {
            return mPid;
        int getUid() {
            return mUid;
        }

        boolean isPrivileged() {
            return mIsPrivileged;
        }

        AudioDeviceAttributes getDevice() {
            return mDevice;
        }

        public void setPlaybackActive(boolean active) {
            mPlaybackActive = active;
        }

        public void setRecordingActive(boolean active) {
            mRecordingActive = active;
        }

        public boolean isActive() {
            return mIsPrivileged || mRecordingActive || mPlaybackActive;
        }

        @Override
        public String toString() {
            return "[CommunicationRouteClient: mUid: " + mUid
                    + " mDevice: " + mDevice.toString()
                    + " mIsPrivileged: " + mIsPrivileged
                    + " mPlaybackActive: " + mPlaybackActive
                    + " mRecordingActive: " + mRecordingActive + "]";
        }
    }

    // @GuardedBy("mSetModeLock")
@@ -2154,8 +2194,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
            return;
        }
        Log.w(TAG, "Communication client died");
        setCommunicationRouteForClient(client.getBinder(), client.getPid(), null,
                BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
        setCommunicationRouteForClient(client.getBinder(), client.getUid(), null,
                BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
                "onCommunicationRouteClientDied");
    }

    /**
@@ -2242,8 +2283,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
                    + crc + " eventSource: " + eventSource);
        }
        if (crc != null) {
            setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(),
                    BtHelper.SCO_MODE_UNDEFINED, eventSource);
            setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
                    BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
        }
    }

@@ -2282,11 +2323,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
    }

    @GuardedBy("mDeviceStateLock")
    private CommunicationRouteClient addCommunicationRouteClient(
                    IBinder cb, int pid, AudioDeviceAttributes device) {
    private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid,
                AudioDeviceAttributes device, boolean isPrivileged) {
        // always insert new request at first position
        removeCommunicationRouteClient(cb, true);
        CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device);
        CommunicationRouteClient client =
                new CommunicationRouteClient(cb, uid, device, isPrivileged);
        if (client.registerDeathRecipient()) {
            mCommunicationRouteClients.add(0, client);
            return client;
@@ -2295,9 +2337,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
    }

    @GuardedBy("mDeviceStateLock")
    private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) {
    private CommunicationRouteClient getCommunicationRouteClientForUid(int uid) {
        for (CommunicationRouteClient cl : mCommunicationRouteClients) {
            if (cl.getPid() == pid) {
            if (cl.getUid() == uid) {
                return cl;
            }
        }
@@ -2330,6 +2372,45 @@ import java.util.concurrent.atomic.AtomicBoolean;
        return device;
    }

    void updateCommunicationRouteClientsActivity(
            List<AudioPlaybackConfiguration> playbackConfigs,
            List<AudioRecordingConfiguration> recordConfigs) {
        synchronized (mSetModeLock) {
            synchronized (mDeviceStateLock) {
                boolean updateCommunicationRoute = false;
                for (CommunicationRouteClient crc : mCommunicationRouteClients) {
                    boolean wasActive = crc.isActive();
                    if (playbackConfigs != null) {
                        crc.setPlaybackActive(false);
                        for (AudioPlaybackConfiguration config : playbackConfigs) {
                            if (config.getClientUid() == crc.getUid()
                                    && config.isActive()) {
                                crc.setPlaybackActive(true);
                                break;
                            }
                        }
                    }
                    if (recordConfigs != null) {
                        crc.setRecordingActive(false);
                        for (AudioRecordingConfiguration config : recordConfigs) {
                            if (config.getClientUid() == crc.getUid()
                                    && !config.isClientSilenced()) {
                                crc.setRecordingActive(true);
                                break;
                            }
                        }
                    }
                    if (wasActive != crc.isActive()) {
                        updateCommunicationRoute = true;
                    }
                }
                if (updateCommunicationRoute) {
                    postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity");
                }
            }
        }
    }

    @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
        synchronized (mDeviceStateLock) {
            return mDeviceInventory.getDeviceSensorUuid(device);
+65 −51

File changed.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -227,8 +227,8 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin
        synchronized (mRecordStates) {
            for (RecordingState state : mRecordStates) {
                // Note: isActiveConfiguration() == true => state.getConfig() != null
                if (state.isActiveConfiguration()
                        && state.getConfig().getClientUid() == uid) {
                if (state.isActiveConfiguration() && state.getConfig().getClientUid() == uid
                        && !state.getConfig().isClientSilenced()) {
                    return true;
                }
            }