1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.telecom; 18 19 import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED; 20 import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE; 21 import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE; 22 23 import android.bluetooth.BluetoothDevice; 24 import android.media.AudioManager; 25 import android.telecom.Log; 26 import android.util.ArraySet; 27 import android.util.Pair; 28 29 import com.android.server.telecom.bluetooth.BluetoothRouteManager; 30 import com.android.server.telecom.flags.FeatureFlags; 31 32 import java.util.Set; 33 34 /** 35 * Used to represent the intermediate state during audio route switching. 36 * Usually, audio route switching start with a communication device setting request to audio 37 * framework and will be completed with corresponding success broadcasts or messages. Instance of 38 * this class is responsible for tracking the pending success signals according to the original 39 * audio route and the destination audio route of this switching. 40 */ 41 public class PendingAudioRoute { 42 private CallAudioRouteController mCallAudioRouteController; 43 private AudioManager mAudioManager; 44 private BluetoothRouteManager mBluetoothRouteManager; 45 private FeatureFlags mFeatureFlags; 46 /** 47 * The {@link AudioRoute} that this pending audio switching started with 48 */ 49 private AudioRoute mOrigRoute; 50 /** 51 * The expected destination {@link AudioRoute} of this pending audio switching, can be changed 52 * by new switching request during the ongoing switching 53 */ 54 private AudioRoute mDestRoute; 55 private Set<Pair<Integer, String>> mPendingMessages; 56 private boolean mActive; 57 /** 58 * The device that has been set for communication by Telecom 59 */ 60 private @AudioRoute.AudioRouteType int mCommunicationDeviceType = AudioRoute.TYPE_INVALID; 61 PendingAudioRoute(CallAudioRouteController controller, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, FeatureFlags featureFlags)62 PendingAudioRoute(CallAudioRouteController controller, AudioManager audioManager, 63 BluetoothRouteManager bluetoothRouteManager, FeatureFlags featureFlags) { 64 mCallAudioRouteController = controller; 65 mAudioManager = audioManager; 66 mBluetoothRouteManager = bluetoothRouteManager; 67 mFeatureFlags = featureFlags; 68 mPendingMessages = new ArraySet<>(); 69 mActive = false; 70 mCommunicationDeviceType = AudioRoute.TYPE_INVALID; 71 } 72 73 /** 74 * Sets the originating route information, and begins the process of transitioning OUT of the 75 * originating route. 76 * Note: We also pass in whether the destination route is going to be active. This is so that 77 * {@link AudioRoute#onOrigRouteAsPendingRoute(boolean, PendingAudioRoute, AudioManager, 78 * BluetoothRouteManager)} knows whether or not the destination route will be active or not and 79 * can determine whether or not it needs to call {@link AudioManager#clearCommunicationDevice()} 80 * or not. To optimize audio performance we only need to clear the communication device if the 81 * end result is going to be that we are in an inactive state. 82 * @param isOriginActive Whether the origin is active. 83 * @param origRoute The origin. 84 * @param isDestActive Whether the destination will be active. 85 */ setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive, boolean isScoAlreadyConnected)86 void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive, 87 boolean isScoAlreadyConnected) { 88 mActive = isDestActive; 89 origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager, 90 mBluetoothRouteManager, isScoAlreadyConnected); 91 mOrigRoute = origRoute; 92 } 93 getOrigRoute()94 public AudioRoute getOrigRoute() { 95 return mOrigRoute; 96 } 97 setDestRoute(boolean active, AudioRoute destRoute, BluetoothDevice device, boolean isScoAlreadyConnected)98 void setDestRoute(boolean active, AudioRoute destRoute, BluetoothDevice device, 99 boolean isScoAlreadyConnected) { 100 destRoute.onDestRouteAsPendingRoute(active, this, device, 101 mAudioManager, mBluetoothRouteManager, isScoAlreadyConnected); 102 mActive = active; 103 mDestRoute = destRoute; 104 } 105 getDestRoute()106 public AudioRoute getDestRoute() { 107 return mDestRoute; 108 } 109 addMessage(int message, String bluetoothDevice)110 public void addMessage(int message, String bluetoothDevice) { 111 mPendingMessages.add(new Pair<>(message, bluetoothDevice)); 112 } 113 onMessageReceived(Pair<Integer, String> message, String btAddressToExclude)114 public void onMessageReceived(Pair<Integer, String> message, String btAddressToExclude) { 115 Log.i(this, "onMessageReceived: message - %s", message); 116 if (message.first == PENDING_ROUTE_FAILED) { 117 // Fallback to base route 118 if (mFeatureFlags.telecomMetricsSupport()) { 119 mCallAudioRouteController.fallBack(btAddressToExclude); 120 } else { 121 mCallAudioRouteController.sendMessageWithSessionInfo( 122 SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, btAddressToExclude); 123 } 124 return; 125 } 126 127 // Removes the first occurrence of the specified message from this list, if it is present. 128 mPendingMessages.remove(message); 129 evaluatePendingState(); 130 } 131 evaluatePendingState()132 public void evaluatePendingState() { 133 if (mPendingMessages.isEmpty()) { 134 mCallAudioRouteController.sendMessageWithSessionInfo( 135 CallAudioRouteAdapter.EXIT_PENDING_ROUTE); 136 } else { 137 Log.i(this, "evaluatePendingState: mPendingMessages - %s", mPendingMessages); 138 } 139 } 140 clearPendingMessages()141 public void clearPendingMessages() { 142 mPendingMessages.clear(); 143 } 144 clearPendingMessage(Pair<Integer, String> message)145 public void clearPendingMessage(Pair<Integer, String> message) { 146 mPendingMessages.remove(message); 147 } 148 getPendingMessages()149 public Set<Pair<Integer, String>> getPendingMessages() { 150 return mPendingMessages; 151 } 152 153 /** 154 * Whether the destination {@link #getDestRoute()} will be active or not. 155 * @return {@code true} if destination will be active, {@code false} otherwise. 156 */ isActive()157 public boolean isActive() { 158 return mActive; 159 } 160 getCommunicationDeviceType()161 public @AudioRoute.AudioRouteType int getCommunicationDeviceType() { 162 return mCommunicationDeviceType; 163 } 164 setCommunicationDeviceType( @udioRoute.AudioRouteType int communicationDeviceType)165 public void setCommunicationDeviceType( 166 @AudioRoute.AudioRouteType int communicationDeviceType) { 167 mCommunicationDeviceType = communicationDeviceType; 168 } 169 overrideDestRoute(AudioRoute route)170 public void overrideDestRoute(AudioRoute route) { 171 mDestRoute = route; 172 } 173 getFeatureFlags()174 public FeatureFlags getFeatureFlags() { 175 return mFeatureFlags; 176 } 177 178 @Override toString()179 public String toString() { 180 return "PendingAudioRoute{" + 181 ", mOrigRoute=" + mOrigRoute + 182 ", mDestRoute=" + mDestRoute + 183 ", mActive=" + mActive + 184 ", mCommunicationDeviceType=" + mCommunicationDeviceType + 185 '}'; 186 } 187 } 188