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

Commit 378a79ff authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "AudioService: better A2DP (dis)connection testing" into rvc-dev am:...

Merge "AudioService: better A2DP (dis)connection testing" into rvc-dev am: 3aaeffc7 am: f020391f

Change-Id: Ib792bad13c80c22434b04043baecb4821a2c484a
parents b4f3c317 f020391f
Loading
Loading
Loading
Loading
+9 −18
Original line number Original line Diff line number Diff line
@@ -25,7 +25,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioRoutesInfo;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
import android.media.IAudioRoutesObserver;
@@ -38,7 +37,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.PrintWriterPrinter;


@@ -71,6 +69,8 @@ import java.io.PrintWriter;
    private final AudioDeviceInventory mDeviceInventory;
    private final AudioDeviceInventory mDeviceInventory;
    // Manages notifications to BT service
    // Manages notifications to BT service
    private final BtHelper mBtHelper;
    private final BtHelper mBtHelper;
    // Adapter for system_server-reserved operations
    private final SystemServerAdapter mSystemServer;




    //-------------------------------------------------------------------
    //-------------------------------------------------------------------
@@ -97,17 +97,21 @@ import java.io.PrintWriter;
        mAudioService = service;
        mAudioService = service;
        mBtHelper = new BtHelper(this);
        mBtHelper = new BtHelper(this);
        mDeviceInventory = new AudioDeviceInventory(this);
        mDeviceInventory = new AudioDeviceInventory(this);
        mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext);


        init();
        init();
    }
    }


    /** for test purposes only, inject AudioDeviceInventory */
    /** for test purposes only, inject AudioDeviceInventory and adapter for operations running
     *  in system_server */
    AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
    AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
                      @NonNull AudioDeviceInventory mockDeviceInventory) {
                      @NonNull AudioDeviceInventory mockDeviceInventory,
                      @NonNull SystemServerAdapter mockSystemServer) {
        mContext = context;
        mContext = context;
        mAudioService = service;
        mAudioService = service;
        mBtHelper = new BtHelper(this);
        mBtHelper = new BtHelper(this);
        mDeviceInventory = mockDeviceInventory;
        mDeviceInventory = mockDeviceInventory;
        mSystemServer = mockSystemServer;


        init();
        init();
    }
    }
@@ -682,7 +686,7 @@ import java.io.PrintWriter;
    private void onSendBecomingNoisyIntent() {
    private void onSendBecomingNoisyIntent() {
        AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
        AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
                "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
                "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
        sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
        mSystemServer.sendDeviceBecomingNoisyIntent();
    }
    }


    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
@@ -1100,17 +1104,4 @@ import java.io.PrintWriter;
                    time);
                    time);
        }
        }
    }
    }

    //-------------------------------------------------------------
    // internal utilities
    private void sendBroadcastToAll(Intent intent) {
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        final long ident = Binder.clearCallingIdentity();
        try {
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
}
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -1118,7 +1118,7 @@ public class AudioDeviceInventory {
                && AudioSystem.isSingleAudioDeviceType(devices, device)
                && AudioSystem.isSingleAudioDeviceType(devices, device)
                && !mDeviceBroker.hasMediaDynamicPolicy()
                && !mDeviceBroker.hasMediaDynamicPolicy()
                && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) {
                && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) {
            if (!AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
            if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
                    && !mDeviceBroker.hasAudioFocusUsers()) {
                    && !mDeviceBroker.hasAudioFocusUsers()) {
                // no media playback, not a "becoming noisy" situation, otherwise it could cause
                // no media playback, not a "becoming noisy" situation, otherwise it could cause
                // the pausing of some apps that are playing remotely
                // the pausing of some apps that are playing remotely
+5 −80
Original line number Original line Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.server.audio;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioSystem;
import android.media.AudioSystem;
import android.util.Log;


/**
/**
 * Provides an adapter to access functionality of the android.media.AudioSystem class for device
 * Provides an adapter to access functionality of the android.media.AudioSystem class for device
@@ -38,15 +37,6 @@ public class AudioSystemAdapter {
        return new AudioSystemAdapter();
        return new AudioSystemAdapter();
    }
    }


    /**
     * Create an adapter for AudioSystem that always succeeds, and does nothing.
     * Overridden methods can be configured
     * @return a no-op AudioSystem adapter with configurable adapter
     */
    static final @NonNull AudioSystemAdapter getConfigurableAdapter() {
        return new AudioSystemConfigurableAdapter();
    }

    /**
    /**
     * Same as {@link AudioSystem#setDeviceConnectionState(int, int, String, String, int)}
     * Same as {@link AudioSystem#setDeviceConnectionState(int, int, String, String, int)}
     * @param device
     * @param device
@@ -143,75 +133,10 @@ public class AudioSystemAdapter {
        return AudioSystem.setCurrentImeUid(uid);
        return AudioSystem.setCurrentImeUid(uid);
    }
    }


    //--------------------------------------------------------------------
    /**
    protected static class AudioSystemConfigurableAdapter extends AudioSystemAdapter {
     * Same as {@link AudioSystem#isStreamActive(int, int)}
        private static final String TAG = "ASA";
     */
        private boolean mIsMicMuted = false;
    public boolean isStreamActive(int stream, int inPastMs) {
        private boolean mMuteMicrophoneFails = false;
        return AudioSystem.isStreamActive(stream, inPastMs);

        public void configureIsMicrophoneMuted(boolean muted) {
            mIsMicMuted = muted;
        }

        public void configureMuteMicrophoneToFail(boolean fail) {
            mMuteMicrophoneFails = fail;
        }

        //-----------------------------------------------------------------
        // Overrides of AudioSystemAdapter
        @Override
        public int setDeviceConnectionState(int device, int state, String deviceAddress,
                                            String deviceName, int codecFormat) {
            Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %s, %s, 0x%s",
                    Integer.toHexString(device), state, deviceAddress, deviceName,
                    Integer.toHexString(codecFormat)));
            return AudioSystem.AUDIO_STATUS_OK;
        }

        @Override
        public int getDeviceConnectionState(int device, String deviceAddress) {
            return AudioSystem.AUDIO_STATUS_OK;
        }

        @Override
        public int handleDeviceConfigChange(int device, String deviceAddress,
                                                   String deviceName, int codecFormat) {
            return AudioSystem.AUDIO_STATUS_OK;
        }

        @Override
        public int setPreferredDeviceForStrategy(int strategy,
                                                 @NonNull AudioDeviceAttributes device) {
            return AudioSystem.AUDIO_STATUS_OK;
        }

        @Override
        public int removePreferredDeviceForStrategy(int strategy) {
            return AudioSystem.AUDIO_STATUS_OK;
        }

        @Override
        public int setParameters(String keyValuePairs) {
            return AudioSystem.AUDIO_STATUS_OK;
        }

        @Override
        public boolean isMicrophoneMuted() {
            return mIsMicMuted;
        }

        @Override
        public int muteMicrophone(boolean on) {
            if (mMuteMicrophoneFails) {
                return AudioSystem.AUDIO_STATUS_ERROR;
            }
            mIsMicMuted = on;
            return AudioSystem.AUDIO_STATUS_OK;
        }

        @Override
        public int setCurrentImeUid(int uid) {
            return AudioSystem.AUDIO_STATUS_OK;
        }
    }
    }
}
}
+19 −24
Original line number Original line Diff line number Diff line
@@ -21,8 +21,11 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.media.AudioManager;
import android.media.AudioManager;
import android.os.Binder;
import android.os.UserHandle;
import android.os.UserHandle;


import java.util.Objects;

/**
/**
 * Provides an adapter to access functionality reserved to components running in system_server
 * Provides an adapter to access functionality reserved to components running in system_server
 * Functionality such as sending privileged broadcasts is to be accessed through the default
 * Functionality such as sending privileged broadcasts is to be accessed through the default
@@ -32,7 +35,7 @@ public class SystemServerAdapter {


    protected final Context mContext;
    protected final Context mContext;


    private SystemServerAdapter(@Nullable Context context) {
    protected SystemServerAdapter(@Nullable Context context) {
        mContext = context;
        mContext = context;
    }
    }
    /**
    /**
@@ -40,18 +43,10 @@ public class SystemServerAdapter {
     * @return the adapter
     * @return the adapter
     */
     */
    static final @NonNull SystemServerAdapter getDefaultAdapter(Context context) {
    static final @NonNull SystemServerAdapter getDefaultAdapter(Context context) {
        Objects.requireNonNull(context);
        return new SystemServerAdapter(context);
        return new SystemServerAdapter(context);
    }
    }


    /**
     * Create an adapter that does nothing.
     * Use for running non-privileged tests, such as unit tests
     * @return a no-op adapter
     */
    static final @NonNull SystemServerAdapter getNoOpAdapter() {
        return new NoOpSystemServerAdapter();
    }

    /**
    /**
     * @return true if this is supposed to be run in system_server, false otherwise (e.g. for a
     * @return true if this is supposed to be run in system_server, false otherwise (e.g. for a
     *     unit test)
     *     unit test)
@@ -70,21 +65,21 @@ public class SystemServerAdapter {
                UserHandle.ALL);
                UserHandle.ALL);
    }
    }


    //--------------------------------------------------------------------
    /**
    protected static class NoOpSystemServerAdapter extends SystemServerAdapter {
     * Broadcast ACTION_AUDIO_BECOMING_NOISY

     */
        NoOpSystemServerAdapter() {
    public void sendDeviceBecomingNoisyIntent() {
            super(null);
        if (mContext == null) {
        }
            return;

        @Override
        public boolean isPrivileged() {
            return false;
        }
        }

        final Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
        @Override
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        public void sendMicrophoneMuteChangedIntent() {
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
            // no-op
        final long ident = Binder.clearCallingIdentity();
        try {
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
        }
    }
    }
}
}
+77 −19
Original line number Original line Diff line number Diff line
@@ -58,6 +58,7 @@ public class AudioDeviceBrokerTest {
    @Mock private AudioService mMockAudioService;
    @Mock private AudioService mMockAudioService;
    @Spy private AudioDeviceInventory mSpyDevInventory;
    @Spy private AudioDeviceInventory mSpyDevInventory;
    @Spy private AudioSystemAdapter mSpyAudioSystem;
    @Spy private AudioSystemAdapter mSpyAudioSystem;
    private SystemServerAdapter mSystemServer;


    private BluetoothDevice mFakeBtDevice;
    private BluetoothDevice mFakeBtDevice;


@@ -66,9 +67,11 @@ public class AudioDeviceBrokerTest {
        mContext = InstrumentationRegistry.getTargetContext();
        mContext = InstrumentationRegistry.getTargetContext();


        mMockAudioService = mock(AudioService.class);
        mMockAudioService = mock(AudioService.class);
        mSpyAudioSystem = spy(AudioSystemAdapter.getConfigurableAdapter());
        mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
        mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
        mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
        mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory);
        mSystemServer = new NoOpSystemServerAdapter();
        mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory,
                mSystemServer);
        mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
        mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);


        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -79,8 +82,8 @@ public class AudioDeviceBrokerTest {
    @After
    @After
    public void tearDown() throws Exception { }
    public void tearDown() throws Exception { }


    @Test
//    @Test
    public void testSetUpAndTearDown() { }
//    public void testSetUpAndTearDown() { }


    /**
    /**
     * postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for connection:
     * postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for connection:
@@ -90,7 +93,7 @@ public class AudioDeviceBrokerTest {
     */
     */
    @Test
    @Test
    public void testPostA2dpDeviceConnectionChange() throws Exception {
    public void testPostA2dpDeviceConnectionChange() throws Exception {
        Log.i(TAG, "testPostA2dpDeviceConnectionChange");
        Log.i(TAG, "starting testPostA2dpDeviceConnectionChange");
        Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
        Assert.assertNotNull("invalid null BT device", mFakeBtDevice);


        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
@@ -104,13 +107,8 @@ public class AudioDeviceBrokerTest {
                ArgumentMatchers.eq(1) /*a2dpVolume*/
                ArgumentMatchers.eq(1) /*a2dpVolume*/
        );
        );


        final String expectedName = mFakeBtDevice.getName() == null ? "" : mFakeBtDevice.getName();
        // verify the connection was reported to AudioSystem
        verify(mSpyAudioSystem, times(1)).setDeviceConnectionState(
        checkSingleSystemConnection(mFakeBtDevice);
                ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP),
                ArgumentMatchers.eq(AudioSystem.DEVICE_STATE_AVAILABLE),
                ArgumentMatchers.eq(mFakeBtDevice.getAddress()),
                ArgumentMatchers.eq(expectedName),
                anyInt() /*codec*/);
    }
    }


    /**
    /**
@@ -121,31 +119,70 @@ public class AudioDeviceBrokerTest {
     */
     */
    @Test
    @Test
    public void testA2dpDeviceConnectionDisconnectionConnectionChange() throws Exception {
    public void testA2dpDeviceConnectionDisconnectionConnectionChange() throws Exception {
        Log.i(TAG, "testA2dpDeviceConnectionDisconnectionConnectionChange");
        Log.i(TAG, "starting testA2dpDeviceConnectionDisconnectionConnectionChange");


        doTestConnectionDisconnectionReconnection(0);
        doTestConnectionDisconnectionReconnection(0, false,
                // cannot guarantee single connection since commands are posted in separate thread
                // than they are processed
                false);
    }
    }


    /**
    /**
     * Verify device disconnection and reconnection within the BECOMING_NOISY window
     * Verify device disconnection and reconnection within the BECOMING_NOISY window
     * in the absence of media playback
     * @throws Exception
     * @throws Exception
     */
     */
    @Test
    @Test
    public void testA2dpDeviceReconnectionWithinBecomingNoisyDelay() throws Exception {
    public void testA2dpDeviceReconnectionWithinBecomingNoisyDelay() throws Exception {
        Log.i(TAG, "testA2dpDeviceReconnectionWithinBecomingNoisyDelay");
        Log.i(TAG, "starting testA2dpDeviceReconnectionWithinBecomingNoisyDelay");

        doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2,
                false,
                // do not check single connection since the connection command will come much
                // after the disconnection command
                false);
    }


        doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2);
    /**
     * Same as testA2dpDeviceConnectionDisconnectionConnectionChange() but with mock media playback
     * @throws Exception
     */
    @Test
    public void testA2dpConnectionDisconnectionConnectionChange_MediaPlayback() throws Exception {
        Log.i(TAG, "starting testA2dpConnectionDisconnectionConnectionChange_MediaPlayback");

        doTestConnectionDisconnectionReconnection(0, true,
                // guarantee single connection since because of media playback the disconnection
                // is supposed to be delayed, and thus cancelled because of the connection
                true);
    }

    /**
     * Same as testA2dpDeviceReconnectionWithinBecomingNoisyDelay() but with mock media playback
     * @throws Exception
     */
    @Test
    public void testA2dpReconnectionWithinBecomingNoisyDelay_MediaPlayback() throws Exception {
        Log.i(TAG, "starting testA2dpReconnectionWithinBecomingNoisyDelay_MediaPlayback");

        doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2,
                true,
                // guarantee single connection since because of media playback the disconnection
                // is supposed to be delayed, and thus cancelled because of the connection
                true);
    }
    }


    private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection)
    private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection,
            throws Exception {
            boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception {
        when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
        when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
                .thenReturn(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
                .thenReturn(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
        when(mMockAudioService.isInCommunication()).thenReturn(false);
        when(mMockAudioService.isInCommunication()).thenReturn(false);
        when(mMockAudioService.hasMediaDynamicPolicy()).thenReturn(false);
        when(mMockAudioService.hasMediaDynamicPolicy()).thenReturn(false);
        when(mMockAudioService.hasAudioFocusUsers()).thenReturn(false);
        when(mMockAudioService.hasAudioFocusUsers()).thenReturn(false);


        // first connection
        ((NoOpAudioSystemAdapter) mSpyAudioSystem).configureIsStreamActive(mockMediaPlayback);

        // first connection: ensure the device is connected as a starting condition for the test
        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
@@ -169,5 +206,26 @@ public class AudioDeviceBrokerTest {
                ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED));
                ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED));
        Assert.assertTrue("Mock device not connected",
        Assert.assertTrue("Mock device not connected",
                mSpyDevInventory.isA2dpDeviceConnected(mFakeBtDevice));
                mSpyDevInventory.isA2dpDeviceConnected(mFakeBtDevice));

        if (guaranteeSingleConnection) {
            // when the disconnection was expected to be cancelled, there should have been a single
            //  call to AudioSystem to declare the device connected (available)
            checkSingleSystemConnection(mFakeBtDevice);
        }
    }

    /**
     * Verifies the given device was reported to AudioSystem exactly once as available
     * @param btDevice
     * @throws Exception
     */
    private void checkSingleSystemConnection(BluetoothDevice btDevice) throws Exception {
        final String expectedName = btDevice.getName() == null ? "" : btDevice.getName();
        verify(mSpyAudioSystem, times(1)).setDeviceConnectionState(
                ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP),
                ArgumentMatchers.eq(AudioSystem.DEVICE_STATE_AVAILABLE),
                ArgumentMatchers.eq(btDevice.getAddress()),
                ArgumentMatchers.eq(expectedName),
                anyInt() /*codec*/);
    }
    }
}
}
Loading