Loading src/com/android/server/telecom/AudioRoute.java +127 −16 Original line number Diff line number Diff line Loading @@ -23,11 +23,14 @@ import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_OFF; import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON; import android.annotation.IntDef; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothStatusCodes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.telecom.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.telecom.bluetooth.BluetoothRouteManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; Loading @@ -38,6 +41,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; Loading @@ -46,9 +50,10 @@ public class AudioRoute { public static class Factory { private final ScheduledExecutorService mScheduledExecutorService = new ScheduledThreadPoolExecutor(1); private final CompletableFuture<AudioRoute> mAudioRouteFuture = new CompletableFuture<>(); private CompletableFuture<AudioRoute> mAudioRouteFuture; public AudioRoute create(@AudioRouteType int type, String bluetoothAddress, AudioManager audioManager) throws RuntimeException { mAudioRouteFuture = new CompletableFuture(); createRetry(type, bluetoothAddress, audioManager, MAX_CONNECTION_RETRIES); try { return mAudioRouteFuture.get(); Loading @@ -58,8 +63,10 @@ public class AudioRoute { } private void createRetry(@AudioRouteType int type, String bluetoothAddress, AudioManager audioManager, int retryCount) { // Early exit if exceeded max number of retries (and complete the future). if (retryCount == 0) { mAudioRouteFuture.complete(null); return; } Log.i(this, "creating AudioRoute with type %s and address %s, retry count %d", Loading @@ -81,10 +88,17 @@ public class AudioRoute { } } } if (routeInfo == null) { // Try connecting BT device anyway (to handle wearables not showing as available // communication device or LE device not showing up since it may not be the lead // device). if (routeInfo == null && bluetoothAddress == null) { try { mScheduledExecutorService.schedule( () -> createRetry(type, bluetoothAddress, audioManager, retryCount - 1), RETRY_TIME_DELAY, TimeUnit.MILLISECONDS); } catch (RejectedExecutionException e) { Log.e(this, e, "Could not schedule retry for audio routing."); } } else { mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo)); } Loading Loading @@ -222,30 +236,81 @@ public class AudioRoute { // Invoked when entered pending route whose dest route is this route void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, AudioManager audioManager) { BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType); if (pendingAudioRoute.isActive() && !active) { Log.i(this, "clearCommunicationDevice"); audioManager.clearCommunicationDevice(); clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); } else if (active) { // Handle BT routing case. if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) { boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device, audioManager, bluetoothRouteManager); // Special handling for SCO case. if (mAudioRouteType == TYPE_BLUETOOTH_SCO) { // Check if the communication device was set for the device, even if // BluetoothHeadset#connectAudio reports that the SCO connection wasn't // successfully established. boolean scoConnected = audioManager.getCommunicationDevice().equals(mInfo); if (connectedBtAudio || scoConnected) { pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType); } if (connectedBtAudio) { pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED); } else if (!scoConnected) { pendingAudioRoute.onMessageReceived( PENDING_ROUTE_FAILED, mBluetoothAddress); } return; } } else if (mAudioRouteType == TYPE_SPEAKER) { pendingAudioRoute.addMessage(SPEAKER_ON); } if (!audioManager.setCommunicationDevice(mInfo)) { pendingAudioRoute.onMessageReceived(PENDING_ROUTE_FAILED); boolean result = false; List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices(); for (AudioDeviceInfo deviceInfo : devices) { // It's possible for the AudioDeviceInfo to be updated for the BT device so adjust // mInfo accordingly. if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) && mBluetoothAddress .equals(deviceInfo.getAddress())) { mInfo = deviceInfo; } if (deviceInfo.equals(mInfo)) { result = audioManager.setCommunicationDevice(mInfo); if (result) { pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType); } Log.i(this, "Result of setting communication device for audio " + "route (%s) - %b", this, result); break; } } // It's possible that BluetoothStateReceiver needs to report that the device is active // before being able to successfully set the communication device. Refrain from sending // pending route failed message for BT route until the second attempt fails. if (!result && !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) { pendingAudioRoute.onMessageReceived(PENDING_ROUTE_FAILED, null); } } } // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice, // sending SPEAKER_OFF, or disconnecting SCO). void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, AudioManager audioManager) { AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType); if (active) { if (mAudioRouteType == TYPE_BLUETOOTH_SCO) { pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED); } else if (mAudioRouteType == TYPE_SPEAKER) { if (mAudioRouteType == TYPE_SPEAKER) { pendingAudioRoute.addMessage(SPEAKER_OFF); } int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful. if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) { pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED); } } } Loading Loading @@ -282,4 +347,50 @@ public class AudioRoute { + ", Address=" + ((mBluetoothAddress != null) ? mBluetoothAddress : "invalid") + "]"; } private boolean connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { // Ensure that if another BT device was set, it is disconnected before connecting // the new one. AudioRoute currentRoute = pendingAudioRoute.getOrigRoute(); if (currentRoute.getBluetoothAddress() != null && !currentRoute.getBluetoothAddress().equals(device.getAddress())) { clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); } // Connect to the device (explicit handling for HFP devices). boolean success = false; if (device != null) { success = bluetoothRouteManager.getDeviceManager() .connectAudio(device, mAudioRouteType); } Log.i(this, "connectBtAudio: routeToConnectTo = %s, successful = %b", this, success); return success; } private int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute, BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) { // Try to see if there's a previously set device for communication that should be cleared. // This only serves to help in the SCO case to ensure that we disconnect the headset. if (pendingAudioRoute.getCommunicationDeviceType() == AudioRoute.TYPE_INVALID) { return -1; } int result = BluetoothStatusCodes.SUCCESS; if (pendingAudioRoute.getCommunicationDeviceType() == TYPE_BLUETOOTH_SCO) { Log.i(this, "Disconnecting SCO device."); result = bluetoothRouteManager.getDeviceManager().disconnectSco(); } else { Log.i(this, "Clearing communication device for audio type %d.", pendingAudioRoute.getCommunicationDeviceType()); audioManager.clearCommunicationDevice(); } if (result == BluetoothStatusCodes.SUCCESS) { pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID); } return result; } } src/com/android/server/telecom/CallAudioRouteAdapter.java +1 −0 Original line number Diff line number Diff line Loading @@ -134,5 +134,6 @@ public interface CallAudioRouteAdapter { CallAudioState getCurrentCallAudioState(); boolean isHfpDeviceAvailable(); Handler getAdapterHandler(); PendingAudioRoute getPendingAudioRoute(); void dump(IndentingPrintWriter pw); } src/com/android/server/telecom/CallAudioRouteController.java +293 −50 File changed.Preview size limit exceeded, changes collapsed. Show changes src/com/android/server/telecom/CallAudioRouteStateMachine.java +7 −0 Original line number Diff line number Diff line Loading @@ -2095,7 +2095,14 @@ public class CallAudioRouteStateMachine extends StateMachine implements CallAudi return base; } @Override public Handler getAdapterHandler() { return getHandler(); } @Override public PendingAudioRoute getPendingAudioRoute() { // Only used by CallAudioRouteController. return null; } } src/com/android/server/telecom/CallsManager.java +2 −1 Original line number Diff line number Diff line Loading @@ -654,7 +654,8 @@ public class CallsManager extends Call.ListenerBase ); } else { callAudioRouteAdapter = new CallAudioRouteController(context, this, audioServiceFactory, new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager); new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager, statusBarNotifier, featureFlags); } callAudioRouteAdapter.initialize(); bluetoothStateReceiver.setCallAudioRouteAdapter(callAudioRouteAdapter); Loading Loading
src/com/android/server/telecom/AudioRoute.java +127 −16 Original line number Diff line number Diff line Loading @@ -23,11 +23,14 @@ import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_OFF; import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON; import android.annotation.IntDef; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothStatusCodes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.telecom.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.telecom.bluetooth.BluetoothRouteManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; Loading @@ -38,6 +41,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; Loading @@ -46,9 +50,10 @@ public class AudioRoute { public static class Factory { private final ScheduledExecutorService mScheduledExecutorService = new ScheduledThreadPoolExecutor(1); private final CompletableFuture<AudioRoute> mAudioRouteFuture = new CompletableFuture<>(); private CompletableFuture<AudioRoute> mAudioRouteFuture; public AudioRoute create(@AudioRouteType int type, String bluetoothAddress, AudioManager audioManager) throws RuntimeException { mAudioRouteFuture = new CompletableFuture(); createRetry(type, bluetoothAddress, audioManager, MAX_CONNECTION_RETRIES); try { return mAudioRouteFuture.get(); Loading @@ -58,8 +63,10 @@ public class AudioRoute { } private void createRetry(@AudioRouteType int type, String bluetoothAddress, AudioManager audioManager, int retryCount) { // Early exit if exceeded max number of retries (and complete the future). if (retryCount == 0) { mAudioRouteFuture.complete(null); return; } Log.i(this, "creating AudioRoute with type %s and address %s, retry count %d", Loading @@ -81,10 +88,17 @@ public class AudioRoute { } } } if (routeInfo == null) { // Try connecting BT device anyway (to handle wearables not showing as available // communication device or LE device not showing up since it may not be the lead // device). if (routeInfo == null && bluetoothAddress == null) { try { mScheduledExecutorService.schedule( () -> createRetry(type, bluetoothAddress, audioManager, retryCount - 1), RETRY_TIME_DELAY, TimeUnit.MILLISECONDS); } catch (RejectedExecutionException e) { Log.e(this, e, "Could not schedule retry for audio routing."); } } else { mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo)); } Loading Loading @@ -222,30 +236,81 @@ public class AudioRoute { // Invoked when entered pending route whose dest route is this route void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, AudioManager audioManager) { BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType); if (pendingAudioRoute.isActive() && !active) { Log.i(this, "clearCommunicationDevice"); audioManager.clearCommunicationDevice(); clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); } else if (active) { // Handle BT routing case. if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) { boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device, audioManager, bluetoothRouteManager); // Special handling for SCO case. if (mAudioRouteType == TYPE_BLUETOOTH_SCO) { // Check if the communication device was set for the device, even if // BluetoothHeadset#connectAudio reports that the SCO connection wasn't // successfully established. boolean scoConnected = audioManager.getCommunicationDevice().equals(mInfo); if (connectedBtAudio || scoConnected) { pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType); } if (connectedBtAudio) { pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED); } else if (!scoConnected) { pendingAudioRoute.onMessageReceived( PENDING_ROUTE_FAILED, mBluetoothAddress); } return; } } else if (mAudioRouteType == TYPE_SPEAKER) { pendingAudioRoute.addMessage(SPEAKER_ON); } if (!audioManager.setCommunicationDevice(mInfo)) { pendingAudioRoute.onMessageReceived(PENDING_ROUTE_FAILED); boolean result = false; List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices(); for (AudioDeviceInfo deviceInfo : devices) { // It's possible for the AudioDeviceInfo to be updated for the BT device so adjust // mInfo accordingly. if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) && mBluetoothAddress .equals(deviceInfo.getAddress())) { mInfo = deviceInfo; } if (deviceInfo.equals(mInfo)) { result = audioManager.setCommunicationDevice(mInfo); if (result) { pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType); } Log.i(this, "Result of setting communication device for audio " + "route (%s) - %b", this, result); break; } } // It's possible that BluetoothStateReceiver needs to report that the device is active // before being able to successfully set the communication device. Refrain from sending // pending route failed message for BT route until the second attempt fails. if (!result && !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) { pendingAudioRoute.onMessageReceived(PENDING_ROUTE_FAILED, null); } } } // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice, // sending SPEAKER_OFF, or disconnecting SCO). void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, AudioManager audioManager) { AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType); if (active) { if (mAudioRouteType == TYPE_BLUETOOTH_SCO) { pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED); } else if (mAudioRouteType == TYPE_SPEAKER) { if (mAudioRouteType == TYPE_SPEAKER) { pendingAudioRoute.addMessage(SPEAKER_OFF); } int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful. if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) { pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED); } } } Loading Loading @@ -282,4 +347,50 @@ public class AudioRoute { + ", Address=" + ((mBluetoothAddress != null) ? mBluetoothAddress : "invalid") + "]"; } private boolean connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { // Ensure that if another BT device was set, it is disconnected before connecting // the new one. AudioRoute currentRoute = pendingAudioRoute.getOrigRoute(); if (currentRoute.getBluetoothAddress() != null && !currentRoute.getBluetoothAddress().equals(device.getAddress())) { clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); } // Connect to the device (explicit handling for HFP devices). boolean success = false; if (device != null) { success = bluetoothRouteManager.getDeviceManager() .connectAudio(device, mAudioRouteType); } Log.i(this, "connectBtAudio: routeToConnectTo = %s, successful = %b", this, success); return success; } private int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute, BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) { // Try to see if there's a previously set device for communication that should be cleared. // This only serves to help in the SCO case to ensure that we disconnect the headset. if (pendingAudioRoute.getCommunicationDeviceType() == AudioRoute.TYPE_INVALID) { return -1; } int result = BluetoothStatusCodes.SUCCESS; if (pendingAudioRoute.getCommunicationDeviceType() == TYPE_BLUETOOTH_SCO) { Log.i(this, "Disconnecting SCO device."); result = bluetoothRouteManager.getDeviceManager().disconnectSco(); } else { Log.i(this, "Clearing communication device for audio type %d.", pendingAudioRoute.getCommunicationDeviceType()); audioManager.clearCommunicationDevice(); } if (result == BluetoothStatusCodes.SUCCESS) { pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID); } return result; } }
src/com/android/server/telecom/CallAudioRouteAdapter.java +1 −0 Original line number Diff line number Diff line Loading @@ -134,5 +134,6 @@ public interface CallAudioRouteAdapter { CallAudioState getCurrentCallAudioState(); boolean isHfpDeviceAvailable(); Handler getAdapterHandler(); PendingAudioRoute getPendingAudioRoute(); void dump(IndentingPrintWriter pw); }
src/com/android/server/telecom/CallAudioRouteController.java +293 −50 File changed.Preview size limit exceeded, changes collapsed. Show changes
src/com/android/server/telecom/CallAudioRouteStateMachine.java +7 −0 Original line number Diff line number Diff line Loading @@ -2095,7 +2095,14 @@ public class CallAudioRouteStateMachine extends StateMachine implements CallAudi return base; } @Override public Handler getAdapterHandler() { return getHandler(); } @Override public PendingAudioRoute getPendingAudioRoute() { // Only used by CallAudioRouteController. return null; } }
src/com/android/server/telecom/CallsManager.java +2 −1 Original line number Diff line number Diff line Loading @@ -654,7 +654,8 @@ public class CallsManager extends Call.ListenerBase ); } else { callAudioRouteAdapter = new CallAudioRouteController(context, this, audioServiceFactory, new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager); new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager, statusBarNotifier, featureFlags); } callAudioRouteAdapter.initialize(); bluetoothStateReceiver.setCallAudioRouteAdapter(callAudioRouteAdapter); Loading