Loading services/core/java/com/android/server/audio/AudioDeviceBroker.java +72 −42 Original line number Diff line number Diff line Loading @@ -15,9 +15,6 @@ */ package com.android.server.audio; import static com.android.server.audio.AudioService.CONNECTION_STATE_CONNECTED; import static com.android.server.audio.AudioService.CONNECTION_STATE_DISCONNECTED; import android.annotation.NonNull; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothDevice; Loading Loading @@ -95,13 +92,28 @@ import java.io.PrintWriter; /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) { mContext = context; mAudioService = service; setupMessaging(context); mBtHelper = new BtHelper(this); mDeviceInventory = new AudioDeviceInventory(this); init(); } /** for test purposes only, inject AudioDeviceInventory */ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service, @NonNull AudioDeviceInventory mockDeviceInventory) { mContext = context; mAudioService = service; mBtHelper = new BtHelper(this); mDeviceInventory = mockDeviceInventory; init(); } private void init() { setupMessaging(mContext); mForcedUseForComm = AudioSystem.FORCE_NONE; mForcedUseForCommExt = mForcedUseForComm; } /*package*/ Context getContext() { Loading Loading @@ -232,17 +244,42 @@ import java.io.PrintWriter; mSupprNoisy = suppressNoisyIntent; mVolume = vol; } // redefine equality op so we can match messages intended for this device @Override public boolean equals(Object o) { return mDevice.equals(o); } } /*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile, suppressNoisyIntent, a2dpVolume); // TODO add a check to try to remove unprocessed messages for the same device (the old // check didn't work), and make sure it doesn't conflict with config change message sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); // when receiving a request to change the connection state of a device, this last request // is the source of truth, so cancel all previous requests removeAllA2dpConnectionEvents(device); sendLMsgNoDelay( state == BluetoothProfile.STATE_CONNECTED ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION, SENDMSG_QUEUE, info); } /** remove all previously scheduled connection and disconnection events for the given device */ private void removeAllA2dpConnectionEvents(@NonNull BluetoothDevice device) { mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION, device); mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION, device); mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, device); mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, device); } private static final class HearingAidDeviceConnectionInfo { Loading Loading @@ -430,13 +467,16 @@ import java.io.PrintWriter; sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE); } /*package*/ void postA2dpSinkConnection(int state, /*package*/ void postA2dpSinkConnection(@AudioService.BtProfileConnectionState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, sendILMsg(state == BluetoothA2dp.STATE_CONNECTED ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, SENDMSG_QUEUE, state, btDeviceInfo, delay); } /*package*/ void postA2dpSourceConnection(int state, /*package*/ void postA2dpSourceConnection(@AudioService.BtProfileConnectionState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state, btDeviceInfo, delay); Loading Loading @@ -522,25 +562,6 @@ import java.io.PrintWriter; } } @GuardedBy("mDeviceStateLock") /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED; final int delay = mDeviceInventory.checkSendBecomingNoisyIntent( AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, AudioSystem.DEVICE_NONE); final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress(); if (AudioService.DEBUG_DEVICES) { Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo + " state= " + state + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock()); } sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, state, btDeviceInfo, delay); } /*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; Loading Loading @@ -575,8 +596,10 @@ import java.io.PrintWriter; // must be called synchronized on mConnectedDevices /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) { return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, new BtHelper.BluetoothA2dpDeviceInfo(btDevice)); return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, new BtHelper.BluetoothA2dpDeviceInfo(btDevice)) || mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, new BtHelper.BluetoothA2dpDeviceInfo(btDevice))); } /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) { Loading Loading @@ -711,7 +734,8 @@ import java.io.PrintWriter; mDeviceInventory.onReportNewRoutes(); } break; case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED: synchronized (mDeviceStateLock) { mDeviceInventory.onSetA2dpSinkConnectionState( (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); Loading Loading @@ -836,7 +860,8 @@ import java.io.PrintWriter; } } break; case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: { case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: { final BtDeviceConnectionInfo info = (BtDeviceConnectionInfo) msg.obj; AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent " Loading Loading @@ -887,7 +912,7 @@ import java.io.PrintWriter; private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3; private static final int MSG_IIL_SET_FORCE_USE = 4; private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5; private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6; private static final int MSG_TOGGLE_HDMI = 6; private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7; private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8; private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; Loading @@ -898,7 +923,6 @@ import java.io.PrintWriter; private static final int MSG_II_SET_HEARING_AID_VOLUME = 14; private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15; private static final int MSG_I_DISCONNECT_BT_SCO = 16; private static final int MSG_TOGGLE_HDMI = 17; private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18; private static final int MSG_DISCONNECT_A2DP = 19; private static final int MSG_DISCONNECT_A2DP_SINK = 20; Loading @@ -908,25 +932,30 @@ import java.io.PrintWriter; private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK = 24; private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID = 25; private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET = 26; private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED = 27; private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED = 28; // process external command to (dis)connect an A2DP device private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 27; private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION = 29; private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION = 30; // process external command to (dis)connect a hearing aid device private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 28; private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31; // a ScoClient died in BtHelper private static final int MSG_L_SCOCLIENT_DIED = 29; private static final int MSG_L_SCOCLIENT_DIED = 32; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED: case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: case MSG_IL_BTA2DP_DOCK_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_TOGGLE_HDMI: case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: return true; default: Loading Loading @@ -1007,7 +1036,8 @@ import java.io.PrintWriter; switch (msg) { case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED: case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_DOCK_TIMEOUT: Loading services/core/java/com/android/server/audio/AudioDeviceInventory.java +70 −26 Original line number Diff line number Diff line Loading @@ -41,14 +41,16 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; /** * Class to manage the inventory of all connected devices. * This class is thread-safe. * (non final for mocking/spying) */ public final class AudioDeviceInventory { public class AudioDeviceInventory { private static final String TAG = "AS.AudioDeviceInventory"; Loading @@ -56,11 +58,7 @@ public final class AudioDeviceInventory { // Key for map created from DeviceInfo.makeDeviceListKey() private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>(); private final @NonNull AudioDeviceBroker mDeviceBroker; AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; } private @NonNull AudioDeviceBroker mDeviceBroker; // cache of the address of the last dock the device was connected to private String mDockAddress; Loading @@ -70,6 +68,20 @@ public final class AudioDeviceInventory { final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = new RemoteCallbackList<IAudioRoutesObserver>(); /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; } //----------------------------------------------------------- /** for mocking only */ /*package*/ AudioDeviceInventory() { mDeviceBroker = null; } /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; } //------------------------------------------------------------ /** * Class to store info about connected devices. Loading Loading @@ -146,8 +158,10 @@ public final class AudioDeviceInventory { } } // only public for mocking/spying @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, @VisibleForTesting public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, @AudioService.BtProfileConnectionState int state) { final BluetoothDevice btDevice = btInfo.getBtDevice(); int a2dpVolume = btInfo.getVolume(); Loading @@ -159,19 +173,28 @@ public final class AudioDeviceInventory { if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } final int a2dpCodec = btInfo.getCodec(); AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( "A2DP sink connected: device addr=" + address + " state=" + state + " codec=" + a2dpCodec + " vol=" + a2dpVolume)); final int a2dpCodec = btInfo.getCodec(); synchronized (mConnectedDevices) { final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, btDevice.getAddress()); final DeviceInfo di = mConnectedDevices.get(key); boolean isConnected = di != null; if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { if (isConnected) { if (state == BluetoothProfile.STATE_CONNECTED) { // device is already connected, but we are receiving a connection again, // it could be for a codec change if (a2dpCodec != di.mDeviceCodecFormat) { mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice); } } else { if (btDevice.isBluetoothDock()) { if (state == BluetoothProfile.STATE_DISCONNECTED) { // introduction of a delay for transient disconnections of docks when Loading @@ -184,6 +207,7 @@ public final class AudioDeviceInventory { } else { makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); } } } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { if (btDevice.isBluetoothDock()) { // this could be a reconnection after a transient disconnection Loading Loading @@ -282,11 +306,9 @@ public final class AudioDeviceInventory { + " event=" + BtHelper.a2dpDeviceEventToString(event))); synchronized (mConnectedDevices) { //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo // for this type of message if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( "A2dp config change ignored")); "A2dp config change ignored (scheduled connection change)")); return; } final String key = DeviceInfo.makeDeviceListKey( Loading Loading @@ -534,8 +556,10 @@ public final class AudioDeviceInventory { return mCurAudioRoutes; } // only public for mocking/spying @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ void setBluetoothA2dpDeviceConnectionState( @VisibleForTesting public void setBluetoothA2dpDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) { int delay; Loading @@ -544,9 +568,12 @@ public final class AudioDeviceInventory { } synchronized (mConnectedDevices) { if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; @AudioService.ConnectionState int asState = (state == BluetoothA2dp.STATE_CONNECTED) ? AudioService.CONNECTION_STATE_CONNECTED : AudioService.CONNECTION_STATE_DISCONNECTED; delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, musicDevice); asState, musicDevice); } else { delay = 0; } Loading Loading @@ -785,7 +812,7 @@ public final class AudioDeviceInventory { return 0; } mDeviceBroker.postBroadcastBecomingNoisy(); delay = 1000; delay = AudioService.BECOMING_NOISY_DELAY_MS; } return delay; Loading Loading @@ -943,4 +970,21 @@ public final class AudioDeviceInventory { intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); } } //---------------------------------------------------------- // For tests only /** * Check if device is in the list of connected devices * @param device * @return true if connected */ @VisibleForTesting public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, device.getAddress()); synchronized (mConnectedDevices) { return (mConnectedDevices.get(key) != null); } } } services/core/java/com/android/server/audio/AudioService.java +29 −7 Original line number Diff line number Diff line Loading @@ -128,6 +128,7 @@ import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; Loading Loading @@ -189,6 +190,13 @@ public class AudioService extends IAudioService.Stub /** How long to delay after a volume down event before unmuting a stream */ private static final int UNMUTE_STREAM_DELAY = 350; /** * Delay before disconnecting a device that would cause BECOMING_NOISY intent to be sent, * to give a chance to applications to pause. */ @VisibleForTesting public static final int BECOMING_NOISY_DELAY_MS = 1000; /** * Only used in the result from {@link #checkForRingerModeChange(int, int, int)} */ Loading Loading @@ -3950,7 +3958,9 @@ public class AudioService extends IAudioService.Stub || adjust == AudioManager.ADJUST_TOGGLE_MUTE; } /*package*/ boolean isInCommunication() { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public boolean isInCommunication() { boolean IsInCall = false; TelecomManager telecomManager = Loading Loading @@ -4119,7 +4129,9 @@ public class AudioService extends IAudioService.Stub return false; } /*package*/ int getDeviceForStream(int stream) { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public int getDeviceForStream(int stream) { int device = getDevicesForStream(stream); if ((device & (device - 1)) != 0) { // Multiple device selection is either: Loading Loading @@ -4164,7 +4176,9 @@ public class AudioService extends IAudioService.Stub } } /*package*/ void postObserveDevicesForAllStreams() { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public void postObserveDevicesForAllStreams() { sendMsg(mAudioHandler, MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS, SENDMSG_QUEUE, 0 /*arg1*/, 0 /*arg2*/, null /*obj*/, Loading Loading @@ -4275,7 +4289,9 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_HDMI; /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public void postAccessoryPlugMediaUnmute(int newDevice) { sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, newDevice, 0, null, 0); } Loading Loading @@ -4825,7 +4841,9 @@ public class AudioService extends IAudioService.Stub } } /*package*/ void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device, /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device, String caller) { sendMsg(mAudioHandler, MSG_SET_DEVICE_STREAM_VOLUME, Loading Loading @@ -5183,7 +5201,9 @@ public class AudioService extends IAudioService.Stub * @return true if there is currently a registered dynamic mixing policy that affects media * and is not a render + loopback policy */ /*package*/ boolean hasMediaDynamicPolicy() { // only public for mocking/spying @VisibleForTesting public boolean hasMediaDynamicPolicy() { synchronized (mAudioPolicies) { if (mAudioPolicies.isEmpty()) { return false; Loading Loading @@ -5516,7 +5536,9 @@ public class AudioService extends IAudioService.Stub return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr); } /*package*/ boolean hasAudioFocusUsers() { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public boolean hasAudioFocusUsers() { return mMediaFocusControl.hasAudioFocusUsers(); } Loading services/core/java/com/android/server/audio/BtHelper.java +9 −3 Original line number Diff line number Diff line Loading @@ -139,6 +139,12 @@ public class BtHelper { public int getCodec() { return mCodec; } // redefine equality op so we can match messages intended for this device @Override public boolean equals(Object o) { return mBtDevice.equals(o); } } // A2DP device events Loading Loading @@ -441,9 +447,9 @@ public class BtHelper { return; } final BluetoothDevice btDevice = deviceList.get(0); final @BluetoothProfile.BtProfileState int state = mA2dp.getConnectionState(btDevice); mDeviceBroker.handleSetA2dpSinkConnectionState( state, new BluetoothA2dpDeviceInfo(btDevice)); // the device is guaranteed CONNECTED mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(btDevice, BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, true, -1); } /*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) { Loading services/tests/servicestests/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" Loading Loading
services/core/java/com/android/server/audio/AudioDeviceBroker.java +72 −42 Original line number Diff line number Diff line Loading @@ -15,9 +15,6 @@ */ package com.android.server.audio; import static com.android.server.audio.AudioService.CONNECTION_STATE_CONNECTED; import static com.android.server.audio.AudioService.CONNECTION_STATE_DISCONNECTED; import android.annotation.NonNull; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothDevice; Loading Loading @@ -95,13 +92,28 @@ import java.io.PrintWriter; /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) { mContext = context; mAudioService = service; setupMessaging(context); mBtHelper = new BtHelper(this); mDeviceInventory = new AudioDeviceInventory(this); init(); } /** for test purposes only, inject AudioDeviceInventory */ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service, @NonNull AudioDeviceInventory mockDeviceInventory) { mContext = context; mAudioService = service; mBtHelper = new BtHelper(this); mDeviceInventory = mockDeviceInventory; init(); } private void init() { setupMessaging(mContext); mForcedUseForComm = AudioSystem.FORCE_NONE; mForcedUseForCommExt = mForcedUseForComm; } /*package*/ Context getContext() { Loading Loading @@ -232,17 +244,42 @@ import java.io.PrintWriter; mSupprNoisy = suppressNoisyIntent; mVolume = vol; } // redefine equality op so we can match messages intended for this device @Override public boolean equals(Object o) { return mDevice.equals(o); } } /*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, int profile, boolean suppressNoisyIntent, int a2dpVolume) { final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile, suppressNoisyIntent, a2dpVolume); // TODO add a check to try to remove unprocessed messages for the same device (the old // check didn't work), and make sure it doesn't conflict with config change message sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); // when receiving a request to change the connection state of a device, this last request // is the source of truth, so cancel all previous requests removeAllA2dpConnectionEvents(device); sendLMsgNoDelay( state == BluetoothProfile.STATE_CONNECTED ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION, SENDMSG_QUEUE, info); } /** remove all previously scheduled connection and disconnection events for the given device */ private void removeAllA2dpConnectionEvents(@NonNull BluetoothDevice device) { mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION, device); mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION, device); mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, device); mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, device); } private static final class HearingAidDeviceConnectionInfo { Loading Loading @@ -430,13 +467,16 @@ import java.io.PrintWriter; sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE); } /*package*/ void postA2dpSinkConnection(int state, /*package*/ void postA2dpSinkConnection(@AudioService.BtProfileConnectionState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, sendILMsg(state == BluetoothA2dp.STATE_CONNECTED ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, SENDMSG_QUEUE, state, btDeviceInfo, delay); } /*package*/ void postA2dpSourceConnection(int state, /*package*/ void postA2dpSourceConnection(@AudioService.BtProfileConnectionState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) { sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state, btDeviceInfo, delay); Loading Loading @@ -522,25 +562,6 @@ import java.io.PrintWriter; } } @GuardedBy("mDeviceStateLock") /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED; final int delay = mDeviceInventory.checkSendBecomingNoisyIntent( AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, AudioSystem.DEVICE_NONE); final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress(); if (AudioService.DEBUG_DEVICES) { Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo + " state= " + state + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock()); } sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE, state, btDeviceInfo, delay); } /*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state, @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; Loading Loading @@ -575,8 +596,10 @@ import java.io.PrintWriter; // must be called synchronized on mConnectedDevices /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) { return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, new BtHelper.BluetoothA2dpDeviceInfo(btDevice)); return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, new BtHelper.BluetoothA2dpDeviceInfo(btDevice)) || mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, new BtHelper.BluetoothA2dpDeviceInfo(btDevice))); } /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) { Loading Loading @@ -711,7 +734,8 @@ import java.io.PrintWriter; mDeviceInventory.onReportNewRoutes(); } break; case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED: synchronized (mDeviceStateLock) { mDeviceInventory.onSetA2dpSinkConnectionState( (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1); Loading Loading @@ -836,7 +860,8 @@ import java.io.PrintWriter; } } break; case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: { case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: { final BtDeviceConnectionInfo info = (BtDeviceConnectionInfo) msg.obj; AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent " Loading Loading @@ -887,7 +912,7 @@ import java.io.PrintWriter; private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3; private static final int MSG_IIL_SET_FORCE_USE = 4; private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5; private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6; private static final int MSG_TOGGLE_HDMI = 6; private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7; private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8; private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; Loading @@ -898,7 +923,6 @@ import java.io.PrintWriter; private static final int MSG_II_SET_HEARING_AID_VOLUME = 14; private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15; private static final int MSG_I_DISCONNECT_BT_SCO = 16; private static final int MSG_TOGGLE_HDMI = 17; private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18; private static final int MSG_DISCONNECT_A2DP = 19; private static final int MSG_DISCONNECT_A2DP_SINK = 20; Loading @@ -908,25 +932,30 @@ import java.io.PrintWriter; private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK = 24; private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID = 25; private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET = 26; private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED = 27; private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED = 28; // process external command to (dis)connect an A2DP device private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 27; private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION = 29; private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION = 30; // process external command to (dis)connect a hearing aid device private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 28; private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31; // a ScoClient died in BtHelper private static final int MSG_L_SCOCLIENT_DIED = 29; private static final int MSG_L_SCOCLIENT_DIED = 32; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED: case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: case MSG_IL_BTA2DP_DOCK_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_TOGGLE_HDMI: case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: return true; default: Loading Loading @@ -1007,7 +1036,8 @@ import java.io.PrintWriter; switch (msg) { case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED: case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_DOCK_TIMEOUT: Loading
services/core/java/com/android/server/audio/AudioDeviceInventory.java +70 −26 Original line number Diff line number Diff line Loading @@ -41,14 +41,16 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; /** * Class to manage the inventory of all connected devices. * This class is thread-safe. * (non final for mocking/spying) */ public final class AudioDeviceInventory { public class AudioDeviceInventory { private static final String TAG = "AS.AudioDeviceInventory"; Loading @@ -56,11 +58,7 @@ public final class AudioDeviceInventory { // Key for map created from DeviceInfo.makeDeviceListKey() private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>(); private final @NonNull AudioDeviceBroker mDeviceBroker; AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; } private @NonNull AudioDeviceBroker mDeviceBroker; // cache of the address of the last dock the device was connected to private String mDockAddress; Loading @@ -70,6 +68,20 @@ public final class AudioDeviceInventory { final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = new RemoteCallbackList<IAudioRoutesObserver>(); /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; } //----------------------------------------------------------- /** for mocking only */ /*package*/ AudioDeviceInventory() { mDeviceBroker = null; } /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; } //------------------------------------------------------------ /** * Class to store info about connected devices. Loading Loading @@ -146,8 +158,10 @@ public final class AudioDeviceInventory { } } // only public for mocking/spying @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, @VisibleForTesting public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, @AudioService.BtProfileConnectionState int state) { final BluetoothDevice btDevice = btInfo.getBtDevice(); int a2dpVolume = btInfo.getVolume(); Loading @@ -159,19 +173,28 @@ public final class AudioDeviceInventory { if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } final int a2dpCodec = btInfo.getCodec(); AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( "A2DP sink connected: device addr=" + address + " state=" + state + " codec=" + a2dpCodec + " vol=" + a2dpVolume)); final int a2dpCodec = btInfo.getCodec(); synchronized (mConnectedDevices) { final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, btDevice.getAddress()); final DeviceInfo di = mConnectedDevices.get(key); boolean isConnected = di != null; if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { if (isConnected) { if (state == BluetoothProfile.STATE_CONNECTED) { // device is already connected, but we are receiving a connection again, // it could be for a codec change if (a2dpCodec != di.mDeviceCodecFormat) { mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice); } } else { if (btDevice.isBluetoothDock()) { if (state == BluetoothProfile.STATE_DISCONNECTED) { // introduction of a delay for transient disconnections of docks when Loading @@ -184,6 +207,7 @@ public final class AudioDeviceInventory { } else { makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); } } } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { if (btDevice.isBluetoothDock()) { // this could be a reconnection after a transient disconnection Loading Loading @@ -282,11 +306,9 @@ public final class AudioDeviceInventory { + " event=" + BtHelper.a2dpDeviceEventToString(event))); synchronized (mConnectedDevices) { //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo // for this type of message if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( "A2dp config change ignored")); "A2dp config change ignored (scheduled connection change)")); return; } final String key = DeviceInfo.makeDeviceListKey( Loading Loading @@ -534,8 +556,10 @@ public final class AudioDeviceInventory { return mCurAudioRoutes; } // only public for mocking/spying @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ void setBluetoothA2dpDeviceConnectionState( @VisibleForTesting public void setBluetoothA2dpDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) { int delay; Loading @@ -544,9 +568,12 @@ public final class AudioDeviceInventory { } synchronized (mConnectedDevices) { if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0; @AudioService.ConnectionState int asState = (state == BluetoothA2dp.STATE_CONNECTED) ? AudioService.CONNECTION_STATE_CONNECTED : AudioService.CONNECTION_STATE_DISCONNECTED; delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState, musicDevice); asState, musicDevice); } else { delay = 0; } Loading Loading @@ -785,7 +812,7 @@ public final class AudioDeviceInventory { return 0; } mDeviceBroker.postBroadcastBecomingNoisy(); delay = 1000; delay = AudioService.BECOMING_NOISY_DELAY_MS; } return delay; Loading Loading @@ -943,4 +970,21 @@ public final class AudioDeviceInventory { intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); } } //---------------------------------------------------------- // For tests only /** * Check if device is in the list of connected devices * @param device * @return true if connected */ @VisibleForTesting public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, device.getAddress()); synchronized (mConnectedDevices) { return (mConnectedDevices.get(key) != null); } } }
services/core/java/com/android/server/audio/AudioService.java +29 −7 Original line number Diff line number Diff line Loading @@ -128,6 +128,7 @@ import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; Loading Loading @@ -189,6 +190,13 @@ public class AudioService extends IAudioService.Stub /** How long to delay after a volume down event before unmuting a stream */ private static final int UNMUTE_STREAM_DELAY = 350; /** * Delay before disconnecting a device that would cause BECOMING_NOISY intent to be sent, * to give a chance to applications to pause. */ @VisibleForTesting public static final int BECOMING_NOISY_DELAY_MS = 1000; /** * Only used in the result from {@link #checkForRingerModeChange(int, int, int)} */ Loading Loading @@ -3950,7 +3958,9 @@ public class AudioService extends IAudioService.Stub || adjust == AudioManager.ADJUST_TOGGLE_MUTE; } /*package*/ boolean isInCommunication() { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public boolean isInCommunication() { boolean IsInCall = false; TelecomManager telecomManager = Loading Loading @@ -4119,7 +4129,9 @@ public class AudioService extends IAudioService.Stub return false; } /*package*/ int getDeviceForStream(int stream) { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public int getDeviceForStream(int stream) { int device = getDevicesForStream(stream); if ((device & (device - 1)) != 0) { // Multiple device selection is either: Loading Loading @@ -4164,7 +4176,9 @@ public class AudioService extends IAudioService.Stub } } /*package*/ void postObserveDevicesForAllStreams() { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public void postObserveDevicesForAllStreams() { sendMsg(mAudioHandler, MSG_OBSERVE_DEVICES_FOR_ALL_STREAMS, SENDMSG_QUEUE, 0 /*arg1*/, 0 /*arg2*/, null /*obj*/, Loading Loading @@ -4275,7 +4289,9 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_HDMI; /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public void postAccessoryPlugMediaUnmute(int newDevice) { sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE, newDevice, 0, null, 0); } Loading Loading @@ -4825,7 +4841,9 @@ public class AudioService extends IAudioService.Stub } } /*package*/ void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device, /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public void postSetVolumeIndexOnDevice(int streamType, int vssVolIndex, int device, String caller) { sendMsg(mAudioHandler, MSG_SET_DEVICE_STREAM_VOLUME, Loading Loading @@ -5183,7 +5201,9 @@ public class AudioService extends IAudioService.Stub * @return true if there is currently a registered dynamic mixing policy that affects media * and is not a render + loopback policy */ /*package*/ boolean hasMediaDynamicPolicy() { // only public for mocking/spying @VisibleForTesting public boolean hasMediaDynamicPolicy() { synchronized (mAudioPolicies) { if (mAudioPolicies.isEmpty()) { return false; Loading Loading @@ -5516,7 +5536,9 @@ public class AudioService extends IAudioService.Stub return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr); } /*package*/ boolean hasAudioFocusUsers() { /** only public for mocking/spying, do not call outside of AudioService */ @VisibleForTesting public boolean hasAudioFocusUsers() { return mMediaFocusControl.hasAudioFocusUsers(); } Loading
services/core/java/com/android/server/audio/BtHelper.java +9 −3 Original line number Diff line number Diff line Loading @@ -139,6 +139,12 @@ public class BtHelper { public int getCodec() { return mCodec; } // redefine equality op so we can match messages intended for this device @Override public boolean equals(Object o) { return mBtDevice.equals(o); } } // A2DP device events Loading Loading @@ -441,9 +447,9 @@ public class BtHelper { return; } final BluetoothDevice btDevice = deviceList.get(0); final @BluetoothProfile.BtProfileState int state = mA2dp.getConnectionState(btDevice); mDeviceBroker.handleSetA2dpSinkConnectionState( state, new BluetoothA2dpDeviceInfo(btDevice)); // the device is guaranteed CONNECTED mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(btDevice, BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, true, -1); } /*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) { Loading
services/tests/servicestests/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" Loading