Loading services/core/java/com/android/server/audio/AudioDeviceBroker.java +9 −18 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; //------------------------------------------------------------------- //------------------------------------------------------------------- Loading @@ -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(); } } Loading Loading @@ -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(); } } //--------------------------------------------------------------------- //--------------------------------------------------------------------- Loading Loading @@ -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); } } } } services/core/java/com/android/server/audio/AudioDeviceInventory.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -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 Loading services/core/java/com/android/server/audio/AudioSystemAdapter.java +5 −80 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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; } } } } } services/core/java/com/android/server/audio/SystemServerAdapter.java +19 −24 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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; } } /** /** Loading @@ -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) Loading @@ -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); } } } } } } services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +77 −19 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading @@ -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: Loading @@ -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, Loading @@ -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*/); } } /** /** Loading @@ -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); Loading @@ -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
services/core/java/com/android/server/audio/AudioDeviceBroker.java +9 −18 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; //------------------------------------------------------------------- //------------------------------------------------------------------- Loading @@ -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(); } } Loading Loading @@ -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(); } } //--------------------------------------------------------------------- //--------------------------------------------------------------------- Loading Loading @@ -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); } } } }
services/core/java/com/android/server/audio/AudioDeviceInventory.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -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 Loading
services/core/java/com/android/server/audio/AudioSystemAdapter.java +5 −80 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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; } } } } }
services/core/java/com/android/server/audio/SystemServerAdapter.java +19 −24 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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; } } /** /** Loading @@ -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) Loading @@ -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); } } } } } }
services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +77 −19 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading @@ -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: Loading @@ -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, Loading @@ -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*/); } } /** /** Loading @@ -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); Loading @@ -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*/); } } } }