blob: 7dcb3cdfa304d358e1b2e888a0f9daa32ecd6e61 [file]
/*
* 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 +
'}';
}
}