From 4da775ddb8d44abc4aa223a8f2ef88bd96781bb4 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Fri, 3 Dec 2021 15:33:46 -0800 Subject: [PATCH] AudioManager: test API for device connection Add a test API to simulate the connection/disconnection of an audio device. The simulation of the connection goes as far as AudioService, but doesn't go into the lower layers of the native audio policy or the HAL. Bug: 200218901 Test: atest com.google.android.gts.audioservice.AudioServiceHostTest#testMuteAwaitConnection Change-Id: I36ae9e07b4c7408a0deb471e9b89263c4db21591 --- core/api/test-current.txt | 1 + media/java/android/media/AudioManager.java | 17 +++++++++++ media/java/android/media/IAudioService.aidl | 2 ++ .../server/audio/AudioDeviceBroker.java | 10 ++++++- .../server/audio/AudioDeviceInventory.java | 29 +++++++++++++++---- .../android/server/audio/AudioService.java | 14 +++++++++ 6 files changed, 66 insertions(+), 7 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 10146731491c1..fee4ed85cf4d8 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1458,6 +1458,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable(); method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int); method public void setRampingRingerEnabled(boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean); } public static final class AudioRecord.MetricsConstants { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 9bde8c3e59ccb..d721291ad78f4 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -5775,6 +5775,23 @@ public class AudioManager { } } + /** + * Indicate wired accessory connection state change. + * @param device {@link AudioDeviceAttributes} of the device to "fake-connect" + * @param connected true for connected, false for disconnected + * {@hide} + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device, + boolean connected) { + try { + getService().setTestDeviceConnectionState(device, connected); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Indicate Bluetooth profile connection state change. * Configuration changes for A2DP are indicated by having the same newDevice and diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 67f366473134e..afcbc5769cf0c 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -458,4 +458,6 @@ interface IAudioService { void registerMuteAwaitConnectionDispatcher(in IMuteAwaitConnectionCallback cb, boolean register); + + void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected); } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 3bd723e1187cc..e4ac7be6be590 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -508,6 +508,13 @@ import java.util.concurrent.atomic.AtomicBoolean; } } + /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device, + @AudioService.ConnectionState int state) { + synchronized (mDeviceStateLock) { + mDeviceInventory.setTestDeviceConnectionState(device, state); + } + } + /*package*/ static final class BleVolumeInfo { final int mIndex; final int mMaxIndex; @@ -1008,7 +1015,8 @@ import java.util.concurrent.atomic.AtomicBoolean; /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, String deviceName) { synchronized (mDeviceStateLock) { - return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName); + return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName, + false /*for test*/); } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index fdb2970f6339f..a27e4b77959c2 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -224,6 +224,7 @@ public class AudioDeviceInventory { public final String mAddress; public final String mName; public final String mCaller; + public boolean mForTest = false; /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, String address, String name, String caller) { @@ -521,7 +522,7 @@ public class AudioDeviceInventory { } if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, - wdcs.mType, wdcs.mAddress, wdcs.mName)) { + wdcs.mType, wdcs.mAddress, wdcs.mName, wdcs.mForTest)) { // change of connection state failed, bailout mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") .record(); @@ -681,7 +682,7 @@ public class AudioDeviceInventory { * @param device the device whose connection state is queried * @return true if connected */ - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") + // called with AudioDeviceBroker.mDeviceStateLock lock held public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) { final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(), device.getAddress()); @@ -696,10 +697,12 @@ public class AudioDeviceInventory { * @param device the device type * @param address the address of the device * @param deviceName human-readable name of device + * @param isForTesting if true, not calling AudioSystem for the connection as this is + * just for testing * @return false if an error was reported by AudioSystem */ /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address, - String deviceName) { + String deviceName, boolean isForTesting) { if (AudioService.DEBUG_DEVICES) { Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device) + " address:" + address @@ -722,9 +725,14 @@ public class AudioDeviceInventory { Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); } if (connect && !isConnected) { - final int res = mAudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, - AudioSystem.AUDIO_FORMAT_DEFAULT); + final int res; + if (isForTesting) { + res = AudioSystem.AUDIO_STATUS_OK; + } else { + res = mAudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName, + AudioSystem.AUDIO_FORMAT_DEFAULT); + } if (res != AudioSystem.AUDIO_STATUS_OK) { final String reason = "not connecting device 0x" + Integer.toHexString(device) + " due to command error " + res; @@ -930,6 +938,15 @@ public class AudioDeviceInventory { } } + /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device, + @AudioService.ConnectionState int state) { + final WiredDeviceConnectionState connection = new WiredDeviceConnectionState( + device.getInternalType(), state, device.getAddress(), + "test device", "com.android.server.audio"); + connection.mForTest = true; + onSetWiredDeviceConnectionState(connection); + } + //------------------------------------------------------------------- // Internal utilities diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3eeffcb82e27a..aa33644357be3 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6364,6 +6364,20 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller); } + /** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */ + public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device, + boolean connected) { + Objects.requireNonNull(device); + enforceModifyAudioRoutingPermission(); + mDeviceBroker.setTestDeviceConnectionState(device, + connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED); + // simulate a routing update from native + sendMsg(mAudioHandler, + MSG_ROUTING_UPDATED, + SENDMSG_REPLACE, 0, 0, null, + /*delay*/ 0); + } + /** * @hide * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()