| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.server.telecom; |
| |
| import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED; |
| import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE; |
| import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE; |
| |
| import android.bluetooth.BluetoothDevice; |
| import android.media.AudioDeviceInfo; |
| import android.media.AudioManager; |
| import android.telecom.Log; |
| import android.util.ArraySet; |
| import android.util.Pair; |
| |
| import com.android.server.telecom.bluetooth.BluetoothRouteManager; |
| import com.android.server.telecom.flags.FeatureFlags; |
| |
| import java.util.Set; |
| |
| /** |
| * Used to represent the intermediate state during audio route switching. |
| * Usually, audio route switching start with a communication device setting request to audio |
| * framework and will be completed with corresponding success broadcasts or messages. Instance of |
| * this class is responsible for tracking the pending success signals according to the original |
| * audio route and the destination audio route of this switching. |
| */ |
| public class PendingAudioRoute { |
| private CallAudioRouteController mCallAudioRouteController; |
| private AudioManager mAudioManager; |
| private BluetoothRouteManager mBluetoothRouteManager; |
| private FeatureFlags mFeatureFlags; |
| /** |
| * The {@link AudioRoute} that this pending audio switching started with |
| */ |
| private AudioRoute mOrigRoute; |
| /** |
| * The expected destination {@link AudioRoute} of this pending audio switching, can be changed |
| * by new switching request during the ongoing switching |
| */ |
| private AudioRoute mDestRoute; |
| private Set<Pair<Integer, String>> mPendingMessages; |
| private boolean mActive; |
| /** |
| * The device that has been set for communication by Telecom |
| */ |
| private @AudioRoute.AudioRouteType int mCommunicationDeviceType = AudioRoute.TYPE_INVALID; |
| private final Object mLock = new Object(); |
| |
| PendingAudioRoute(CallAudioRouteController controller, AudioManager audioManager, |
| BluetoothRouteManager bluetoothRouteManager, FeatureFlags featureFlags) { |
| mCallAudioRouteController = controller; |
| mAudioManager = audioManager; |
| mBluetoothRouteManager = bluetoothRouteManager; |
| mFeatureFlags = featureFlags; |
| mPendingMessages = new ArraySet<>(); |
| mActive = false; |
| mCommunicationDeviceType = AudioRoute.TYPE_INVALID; |
| } |
| |
| /** |
| * 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, |
| boolean isScoAlreadyConnected, boolean isDestRouteCommunicationDevice) { |
| mActive = isDestActive; |
| // Skip clearing the communication device or disconnecting SCO when the current |
| // communication device is already associated with the destination route. Ensure we still |
| // clear the communication device at the end of the call. |
| if (!isDestRouteCommunicationDevice || (isOriginActive != isDestActive && !isDestActive)) { |
| origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager, |
| mBluetoothRouteManager, isScoAlreadyConnected); |
| } |
| mOrigRoute = origRoute; |
| } |
| |
| public AudioRoute getOrigRoute() { |
| return mOrigRoute; |
| } |
| |
| void setDestRoute(boolean active, AudioRoute destRoute, BluetoothDevice device, |
| boolean isScoAlreadyConnected, boolean isDestRouteCommunicationDevice, |
| boolean isMovingToActiveRouting) { |
| // Skip setting the communication device when the audio fwk reported communication device |
| // matches up with the destination route unless it's the start of the call. When Telecom |
| // becomes the mode owner, we must always set the communication device due to the previous |
| // focus owner clearing the request (clearCommunicationDevice). We'll just not add the |
| // pending message if the update isn't reflected in the communicate device update callback. |
| if (!isDestRouteCommunicationDevice || isMovingToActiveRouting) { |
| destRoute.onDestRouteAsPendingRoute(active, this, device, |
| mAudioManager, mBluetoothRouteManager, isScoAlreadyConnected); |
| } else { |
| setCommunicationDeviceType(destRoute.getType()); |
| } |
| mActive = active; |
| mDestRoute = destRoute; |
| } |
| |
| public AudioRoute getDestRoute() { |
| return mDestRoute; |
| } |
| |
| public void addMessage(int message, String bluetoothDevice) { |
| synchronized (mLock) { |
| mPendingMessages.add(new Pair<>(message, bluetoothDevice)); |
| } |
| } |
| |
| public void onMessageReceived(Pair<Integer, String> message, String btAddressToExclude) { |
| Log.i(this, "onMessageReceived: message - %s", message); |
| if (message.first == PENDING_ROUTE_FAILED) { |
| // Fallback to base route |
| mCallAudioRouteController.fallBack(btAddressToExclude); |
| return; |
| } |
| |
| synchronized (mLock) { |
| // Removes the first occurrence of the specified message from this list, if it is present. |
| mPendingMessages.remove(message); |
| evaluatePendingState(); |
| } |
| } |
| |
| public void evaluatePendingState() { |
| synchronized (mLock) { |
| if (mPendingMessages.isEmpty()) { |
| mCallAudioRouteController.sendMessageWithSessionInfo( |
| CallAudioRouteAdapter.EXIT_PENDING_ROUTE); |
| } else { |
| Log.i(this, "evaluatePendingState: mPendingMessages - %s", mPendingMessages); |
| } |
| } |
| } |
| |
| public void clearPendingMessages() { |
| synchronized (mLock) { |
| mPendingMessages.clear(); |
| } |
| } |
| |
| public void clearPendingMessage(Pair<Integer, String> message) { |
| synchronized (mLock) { |
| mPendingMessages.remove(message); |
| } |
| } |
| |
| public Set<Pair<Integer, String>> getPendingMessages() { |
| synchronized (mLock) { |
| 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; |
| } |
| |
| public @AudioRoute.AudioRouteType int getCommunicationDeviceType() { |
| return mCommunicationDeviceType; |
| } |
| |
| public void setCommunicationDeviceType( |
| @AudioRoute.AudioRouteType int communicationDeviceType) { |
| mCommunicationDeviceType = communicationDeviceType; |
| } |
| |
| public void overrideDestRoute(AudioRoute route) { |
| mDestRoute = route; |
| } |
| |
| public void setActive(boolean active) { |
| mActive = active; |
| } |
| |
| public FeatureFlags getFeatureFlags() { |
| return mFeatureFlags; |
| } |
| |
| @Override |
| public String toString() { |
| return "PendingAudioRoute{" + |
| ", mOrigRoute=" + mOrigRoute + |
| ", mDestRoute=" + mDestRoute + |
| ", mActive=" + mActive + |
| ", mCommunicationDeviceType=" + mCommunicationDeviceType + |
| '}'; |
| } |
| } |