Loading flags/telecom_callaudioroutestatemachine_flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -129,3 +129,14 @@ flag { purpose: PURPOSE_BUGFIX } } # OWNER=tgunn TARGET=25Q2 flag { name: "only_clear_communication_device_on_inactive" namespace: "telecom" description: "Only clear the communication device when transitioning to an inactive route." bug: "376781369" metadata { purpose: PURPOSE_BUGFIX } } src/com/android/server/telecom/AudioRoute.java +59 −10 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON; import android.annotation.IntDef; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothStatusCodes; import android.media.AudioDeviceInfo; import android.media.AudioManager; Loading Loading @@ -300,7 +301,8 @@ public class AudioRoute { pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType); } Log.i(this, "onDestRouteAsPendingRoute: route=%s, " + "AudioManager#setCommunicationDevice()=%b", this, result); + "AudioManager#setCommunicationDevice(%s)=%b", this, audioDeviceTypeToString(mInfo.getType()), result); break; } } Loading @@ -314,13 +316,19 @@ public class AudioRoute { } } // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice, // sending SPEAKER_OFF, or disconnecting SCO). void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, /** * Takes care of cleaning up original audio route (i.e. clearCommunicationDevice, * sending SPEAKER_OFF, or disconnecting SCO). * @param wasActive Was the origin route active or not. * @param pendingAudioRoute The pending audio route change we're performing. * @param audioManager Good 'ol audio manager. * @param bluetoothRouteManager The BT route manager. */ void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%s)", active, DEVICE_TYPE_STRINGS.get(mAudioRouteType)); if (active) { Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s)", wasActive, DEVICE_TYPE_STRINGS.get(mAudioRouteType), pendingAudioRoute); if (wasActive) { int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); if (mAudioRouteType == TYPE_SPEAKER) { Loading Loading @@ -389,6 +397,20 @@ public class AudioRoute { return success; } /** * Clears the communication device; this takes into account the fact that SCO devices require * us to call {@link BluetoothHeadset#disconnectAudio()} rather than * {@link AudioManager#clearCommunicationDevice()}. * As a general rule, if we are transitioning from an active route to another active route, we * do NOT need to call {@link AudioManager#clearCommunicationDevice()}, but if the device is a * legacy SCO device we WILL need to call {@link BluetoothHeadset#disconnectAudio()}. We rely * on the {@link PendingAudioRoute#isActive()} indicator to tell us if the destination route * is going to be active or not. * @param pendingAudioRoute The pending audio route transition we're implementing. * @param bluetoothRouteManager The BT route manager. * @param audioManager The audio manager. * @return -1 if nothing was done, or the result code from the BT SCO disconnect. */ int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute, BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) { // Try to see if there's a previously set device for communication that should be cleared. Loading @@ -402,10 +424,18 @@ public class AudioRoute { Log.i(this, "clearCommunicationDevice: Disconnecting SCO device."); result = bluetoothRouteManager.getDeviceManager().disconnectSco(); } else { Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s", // Only clear communication device if the destination route will be inactive; route to // route transitions do not require clearing the communication device. boolean onlyClearCommunicationDeviceOnInactive = pendingAudioRoute.getFeatureFlags().onlyClearCommunicationDeviceOnInactive(); if (!onlyClearCommunicationDeviceOnInactive || (onlyClearCommunicationDeviceOnInactive && !pendingAudioRoute.isActive())) { Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s", DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType())); audioManager.clearCommunicationDevice(); } } if (result == BluetoothStatusCodes.SUCCESS) { if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) { Loading @@ -430,4 +460,23 @@ public class AudioRoute { pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null)); } } /** * Get a human readable (for logs) version of an an audio device type. * @param type the device type * @return the human readable string */ private static String audioDeviceTypeToString(int type) { return switch (type) { case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "earpiece"; case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "speaker"; case AudioDeviceInfo.TYPE_BUS -> "bus(auto speaker)"; case AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "bt sco"; case AudioDeviceInfo.TYPE_BLE_HEADSET -> "bt le"; case AudioDeviceInfo.TYPE_HEARING_AID -> "bt hearing aid"; case AudioDeviceInfo.TYPE_USB_HEADSET -> "usb headset"; case AudioDeviceInfo.TYPE_WIRED_HEADSET -> "wired headset"; default -> Integer.toString(type); }; } } src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java +1 −0 Original line number Diff line number Diff line Loading @@ -251,6 +251,7 @@ public class CallAudioCommunicationDeviceTracker { } // Clear device and reset locally saved device type. Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice()"); mAudioManager.clearCommunicationDevice(); mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID; Loading src/com/android/server/telecom/CallAudioRouteController.java +6 −3 Original line number Diff line number Diff line Loading @@ -567,7 +567,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { } // override pending route while keep waiting for still pending messages for the // previous pending route mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute()); mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mPendingAudioRoute.getDestRoute(), active /* dest */); } else { if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) { return; Loading @@ -576,10 +577,12 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { mIsActive, destRoute, active); // route to pending route if (getCallSupportedRoutes().contains(mCurrentRoute)) { mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute); mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mCurrentRoute, active /* dest */); } else { // Avoid waiting for pending messages for an unavailable route mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE); mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, DUMMY_ROUTE, active /* dest */); } mIsPending = true; } Loading src/com/android/server/telecom/PendingAudioRoute.java +31 −2 Original line number Diff line number Diff line Loading @@ -70,8 +70,23 @@ public class PendingAudioRoute { mCommunicationDeviceType = AudioRoute.TYPE_INVALID; } void setOrigRoute(boolean active, AudioRoute origRoute) { origRoute.onOrigRouteAsPendingRoute(active, this, mAudioManager, mBluetoothRouteManager); /** * Sets the originating route information, and begins the process of transitioning OUT of the * originating route. * Note: We also pass in whether the destination route is going to be active. This is so that * {@link AudioRoute#onOrigRouteAsPendingRoute(boolean, PendingAudioRoute, AudioManager, * BluetoothRouteManager)} knows whether or not the destination route will be active or not and * can determine whether or not it needs to call {@link AudioManager#clearCommunicationDevice()} * or not. To optimize audio performance we only need to clear the communication device if the * end result is going to be that we are in an inactive state. * @param isOriginActive Whether the origin is active. * @param origRoute The origin. * @param isDestActive Whether the destination will be active. */ void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive) { mActive = isDestActive; origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager, mBluetoothRouteManager); mOrigRoute = origRoute; } Loading Loading @@ -134,6 +149,10 @@ public class PendingAudioRoute { return mPendingMessages; } /** * Whether the destination {@link #getDestRoute()} will be active or not. * @return {@code true} if destination will be active, {@code false} otherwise. */ public boolean isActive() { return mActive; } Loading @@ -154,4 +173,14 @@ public class PendingAudioRoute { public FeatureFlags getFeatureFlags() { return mFeatureFlags; } @Override public String toString() { return "PendingAudioRoute{" + ", mOrigRoute=" + mOrigRoute + ", mDestRoute=" + mDestRoute + ", mActive=" + mActive + ", mCommunicationDeviceType=" + mCommunicationDeviceType + '}'; } } Loading
flags/telecom_callaudioroutestatemachine_flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -129,3 +129,14 @@ flag { purpose: PURPOSE_BUGFIX } } # OWNER=tgunn TARGET=25Q2 flag { name: "only_clear_communication_device_on_inactive" namespace: "telecom" description: "Only clear the communication device when transitioning to an inactive route." bug: "376781369" metadata { purpose: PURPOSE_BUGFIX } }
src/com/android/server/telecom/AudioRoute.java +59 −10 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON; import android.annotation.IntDef; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothStatusCodes; import android.media.AudioDeviceInfo; import android.media.AudioManager; Loading Loading @@ -300,7 +301,8 @@ public class AudioRoute { pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType); } Log.i(this, "onDestRouteAsPendingRoute: route=%s, " + "AudioManager#setCommunicationDevice()=%b", this, result); + "AudioManager#setCommunicationDevice(%s)=%b", this, audioDeviceTypeToString(mInfo.getType()), result); break; } } Loading @@ -314,13 +316,19 @@ public class AudioRoute { } } // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice, // sending SPEAKER_OFF, or disconnecting SCO). void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, /** * Takes care of cleaning up original audio route (i.e. clearCommunicationDevice, * sending SPEAKER_OFF, or disconnecting SCO). * @param wasActive Was the origin route active or not. * @param pendingAudioRoute The pending audio route change we're performing. * @param audioManager Good 'ol audio manager. * @param bluetoothRouteManager The BT route manager. */ void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%s)", active, DEVICE_TYPE_STRINGS.get(mAudioRouteType)); if (active) { Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s)", wasActive, DEVICE_TYPE_STRINGS.get(mAudioRouteType), pendingAudioRoute); if (wasActive) { int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); if (mAudioRouteType == TYPE_SPEAKER) { Loading Loading @@ -389,6 +397,20 @@ public class AudioRoute { return success; } /** * Clears the communication device; this takes into account the fact that SCO devices require * us to call {@link BluetoothHeadset#disconnectAudio()} rather than * {@link AudioManager#clearCommunicationDevice()}. * As a general rule, if we are transitioning from an active route to another active route, we * do NOT need to call {@link AudioManager#clearCommunicationDevice()}, but if the device is a * legacy SCO device we WILL need to call {@link BluetoothHeadset#disconnectAudio()}. We rely * on the {@link PendingAudioRoute#isActive()} indicator to tell us if the destination route * is going to be active or not. * @param pendingAudioRoute The pending audio route transition we're implementing. * @param bluetoothRouteManager The BT route manager. * @param audioManager The audio manager. * @return -1 if nothing was done, or the result code from the BT SCO disconnect. */ int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute, BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) { // Try to see if there's a previously set device for communication that should be cleared. Loading @@ -402,10 +424,18 @@ public class AudioRoute { Log.i(this, "clearCommunicationDevice: Disconnecting SCO device."); result = bluetoothRouteManager.getDeviceManager().disconnectSco(); } else { Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s", // Only clear communication device if the destination route will be inactive; route to // route transitions do not require clearing the communication device. boolean onlyClearCommunicationDeviceOnInactive = pendingAudioRoute.getFeatureFlags().onlyClearCommunicationDeviceOnInactive(); if (!onlyClearCommunicationDeviceOnInactive || (onlyClearCommunicationDeviceOnInactive && !pendingAudioRoute.isActive())) { Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s", DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType())); audioManager.clearCommunicationDevice(); } } if (result == BluetoothStatusCodes.SUCCESS) { if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) { Loading @@ -430,4 +460,23 @@ public class AudioRoute { pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null)); } } /** * Get a human readable (for logs) version of an an audio device type. * @param type the device type * @return the human readable string */ private static String audioDeviceTypeToString(int type) { return switch (type) { case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "earpiece"; case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "speaker"; case AudioDeviceInfo.TYPE_BUS -> "bus(auto speaker)"; case AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "bt sco"; case AudioDeviceInfo.TYPE_BLE_HEADSET -> "bt le"; case AudioDeviceInfo.TYPE_HEARING_AID -> "bt hearing aid"; case AudioDeviceInfo.TYPE_USB_HEADSET -> "usb headset"; case AudioDeviceInfo.TYPE_WIRED_HEADSET -> "wired headset"; default -> Integer.toString(type); }; } }
src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java +1 −0 Original line number Diff line number Diff line Loading @@ -251,6 +251,7 @@ public class CallAudioCommunicationDeviceTracker { } // Clear device and reset locally saved device type. Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice()"); mAudioManager.clearCommunicationDevice(); mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID; Loading
src/com/android/server/telecom/CallAudioRouteController.java +6 −3 Original line number Diff line number Diff line Loading @@ -567,7 +567,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { } // override pending route while keep waiting for still pending messages for the // previous pending route mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute()); mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mPendingAudioRoute.getDestRoute(), active /* dest */); } else { if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) { return; Loading @@ -576,10 +577,12 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { mIsActive, destRoute, active); // route to pending route if (getCallSupportedRoutes().contains(mCurrentRoute)) { mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute); mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mCurrentRoute, active /* dest */); } else { // Avoid waiting for pending messages for an unavailable route mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE); mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, DUMMY_ROUTE, active /* dest */); } mIsPending = true; } Loading
src/com/android/server/telecom/PendingAudioRoute.java +31 −2 Original line number Diff line number Diff line Loading @@ -70,8 +70,23 @@ public class PendingAudioRoute { mCommunicationDeviceType = AudioRoute.TYPE_INVALID; } void setOrigRoute(boolean active, AudioRoute origRoute) { origRoute.onOrigRouteAsPendingRoute(active, this, mAudioManager, mBluetoothRouteManager); /** * Sets the originating route information, and begins the process of transitioning OUT of the * originating route. * Note: We also pass in whether the destination route is going to be active. This is so that * {@link AudioRoute#onOrigRouteAsPendingRoute(boolean, PendingAudioRoute, AudioManager, * BluetoothRouteManager)} knows whether or not the destination route will be active or not and * can determine whether or not it needs to call {@link AudioManager#clearCommunicationDevice()} * or not. To optimize audio performance we only need to clear the communication device if the * end result is going to be that we are in an inactive state. * @param isOriginActive Whether the origin is active. * @param origRoute The origin. * @param isDestActive Whether the destination will be active. */ void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive) { mActive = isDestActive; origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager, mBluetoothRouteManager); mOrigRoute = origRoute; } Loading Loading @@ -134,6 +149,10 @@ public class PendingAudioRoute { return mPendingMessages; } /** * Whether the destination {@link #getDestRoute()} will be active or not. * @return {@code true} if destination will be active, {@code false} otherwise. */ public boolean isActive() { return mActive; } Loading @@ -154,4 +173,14 @@ public class PendingAudioRoute { public FeatureFlags getFeatureFlags() { return mFeatureFlags; } @Override public String toString() { return "PendingAudioRoute{" + ", mOrigRoute=" + mOrigRoute + ", mDestRoute=" + mDestRoute + ", mActive=" + mActive + ", mCommunicationDeviceType=" + mCommunicationDeviceType + '}'; } }