1 /*
2  * Copyright (C) 2022 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.AudioRoute.BT_AUDIO_DEVICE_INFO_TYPES;
20 
21 import android.bluetooth.BluetoothDevice;
22 import android.content.Context;
23 import android.media.AudioDeviceInfo;
24 import android.media.AudioManager;
25 import android.telecom.Log;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
29 import com.android.server.telecom.flags.Flags;
30 
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.concurrent.Semaphore;
34 import java.util.concurrent.locks.Lock;
35 import java.util.concurrent.locks.ReentrantLock;
36 
37 /**
38  * Helper class used to keep track of the requested communication device within Telecom for audio
39  * use cases. Handles the set/clear communication use case logic for all audio routes (speaker, BT,
40  * headset, and earpiece). For BT devices, this handles switches between hearing aids, SCO, and LE
41  * audio (also takes into account switching between multiple LE audio devices).
42  */
43 public class CallAudioCommunicationDeviceTracker {
44 
45     // Use -1 indicates device is not set for any communication use case
46     private static final int sAUDIO_DEVICE_TYPE_INVALID = -1;
47     private AudioManager mAudioManager;
48     private BluetoothRouteManager mBluetoothRouteManager;
49     private int mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
50     // Keep track of the locally requested BT audio device if set
51     private String mBtAudioDevice = null;
52     private final Lock mLock = new ReentrantLock();
53 
CallAudioCommunicationDeviceTracker(Context context)54     public CallAudioCommunicationDeviceTracker(Context context) {
55         mAudioManager = context.getSystemService(AudioManager.class);
56     }
57 
setBluetoothRouteManager(BluetoothRouteManager bluetoothRouteManager)58     public void setBluetoothRouteManager(BluetoothRouteManager bluetoothRouteManager) {
59         mBluetoothRouteManager = bluetoothRouteManager;
60     }
61 
isAudioDeviceSetForType(int audioDeviceType)62     public boolean isAudioDeviceSetForType(int audioDeviceType) {
63         if (Flags.communicationDeviceProtectedByLock()) {
64             mLock.lock();
65         }
66         try {
67             return mAudioDeviceType == audioDeviceType;
68         } finally {
69             if (Flags.communicationDeviceProtectedByLock()) {
70                 mLock.unlock();
71             }
72         }
73     }
74 
getCurrentLocallyRequestedCommunicationDevice()75     public int getCurrentLocallyRequestedCommunicationDevice() {
76         if (Flags.communicationDeviceProtectedByLock()) {
77             mLock.lock();
78         }
79         try {
80             return mAudioDeviceType;
81         } finally {
82             if (Flags.communicationDeviceProtectedByLock()) {
83                 mLock.unlock();
84             }
85         }
86     }
87 
88     @VisibleForTesting
setTestCommunicationDevice(int audioDeviceType)89     public void setTestCommunicationDevice(int audioDeviceType) {
90         mAudioDeviceType = audioDeviceType;
91     }
92 
clearBtCommunicationDevice()93     public void clearBtCommunicationDevice() {
94         if (Flags.communicationDeviceProtectedByLock()) {
95             mLock.lock();
96         }
97         try {
98             if (mBtAudioDevice == null) {
99                 Log.i(this, "No bluetooth device was set for communication that can be cleared.");
100             } else {
101                 // If mBtAudioDevice is set, we know a BT audio device was set for communication so
102                 // mAudioDeviceType corresponds to a BT device type (e.g. hearing aid, SCO, LE).
103                 processClearCommunicationDevice(mAudioDeviceType);
104             }
105         } finally {
106             if (Flags.communicationDeviceProtectedByLock()) {
107                 mLock.unlock();
108             }
109         }
110     }
111 
112     /*
113      * Sets the communication device for the passed in audio device type, if it's available for
114      * communication use cases. Tries to clear any communication device which was previously
115      * requested for communication before setting the new device.
116      * @param audioDeviceTypes The supported audio device types for the device.
117      * @param btDevice The bluetooth device to connect to (only used for switching between multiple
118      *        LE audio devices).
119      * @return {@code true} if the device was set for communication, {@code false} if the device
120      * wasn't set.
121      */
setCommunicationDevice(int audioDeviceType, BluetoothDevice btDevice)122     public boolean setCommunicationDevice(int audioDeviceType,
123             BluetoothDevice btDevice) {
124         if (Flags.communicationDeviceProtectedByLock()) {
125             mLock.lock();
126         }
127         try {
128             return processSetCommunicationDevice(audioDeviceType, btDevice);
129         } finally {
130             if (Flags.communicationDeviceProtectedByLock()) {
131                 mLock.unlock();
132             }
133         }
134     }
135 
processSetCommunicationDevice(int audioDeviceType, BluetoothDevice btDevice)136     private boolean processSetCommunicationDevice(int audioDeviceType,
137             BluetoothDevice btDevice) {
138         // There is only one audio device type associated with each type of BT device.
139         boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType);
140         Log.i(this, "setCommunicationDevice: type = %s, isBtDevice = %s, btDevice = %s",
141                 audioDeviceType, isBtDevice, btDevice);
142 
143         // Account for switching between multiple LE audio devices.
144         boolean handleLeAudioDeviceSwitch = btDevice != null
145                 && !btDevice.getAddress().equals(mBtAudioDevice);
146         if ((audioDeviceType == mAudioDeviceType
147                 || isUsbHeadsetType(audioDeviceType, mAudioDeviceType))
148                 && !handleLeAudioDeviceSwitch) {
149             Log.i(this, "Communication device is already set for this audio type");
150             return false;
151         }
152 
153         AudioDeviceInfo activeDevice = null;
154         List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
155         if (devices.size() == 0) {
156             Log.w(this, "No communication devices available");
157             return false;
158         }
159 
160         for (AudioDeviceInfo device : devices) {
161             Log.i(this, "Available device type: " + device.getType());
162             // Ensure that we do not select the same BT LE audio device for communication.
163             if ((audioDeviceType == device.getType()
164                     || isUsbHeadsetType(audioDeviceType, device.getType()))
165                     && !device.getAddress().equals(mBtAudioDevice)) {
166                 activeDevice = device;
167                 break;
168             }
169         }
170 
171         if (activeDevice == null) {
172             Log.i(this, "No active device of type(s) %s available",
173                     audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
174                             ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
175                             AudioDeviceInfo.TYPE_USB_HEADSET)
176                             : audioDeviceType);
177             return false;
178         }
179 
180         // Force clear previous communication device, if one was set, before setting the new device.
181         if (mAudioDeviceType != sAUDIO_DEVICE_TYPE_INVALID) {
182             processClearCommunicationDevice(mAudioDeviceType);
183         }
184 
185         // Turn activeDevice ON.
186         boolean result = mAudioManager.setCommunicationDevice(activeDevice);
187         if (!result) {
188             Log.w(this, "Could not set active device");
189         } else {
190             Log.i(this, "Active device set");
191             mAudioDeviceType = activeDevice.getType();
192             if (isBtDevice) {
193                 mBtAudioDevice = activeDevice.getAddress();
194                 if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) {
195                     mBluetoothRouteManager.onAudioOn(mBtAudioDevice);
196                 }
197             } else if (Flags.communicationDeviceProtectedByLock()) {
198                 // Clear BT device if it's still stored. Handles race condition for when a non-BT
199                 // device is set for communication shortly after a BT (LE) device is set for
200                 // communication but the selection hasn't been cleared yet.
201                 mBtAudioDevice = null;
202             }
203         }
204         return result;
205     }
206     /*
207      * Clears the communication device for the passed in audio device types, given that the device
208      * has previously been set for communication.
209      * @param audioDeviceTypes The supported audio device types for the device.
210      */
clearCommunicationDevice(int audioDeviceType)211     public void clearCommunicationDevice(int audioDeviceType) {
212         if (Flags.communicationDeviceProtectedByLock()) {
213             mLock.lock();
214         }
215         try {
216             processClearCommunicationDevice(audioDeviceType);
217         } finally {
218             if (Flags.communicationDeviceProtectedByLock()) {
219                 mLock.unlock();
220             }
221         }
222     }
223 
processClearCommunicationDevice(int audioDeviceType)224     public void processClearCommunicationDevice(int audioDeviceType) {
225         if (audioDeviceType == sAUDIO_DEVICE_TYPE_INVALID) {
226             Log.i(this, "clearCommunicationDevice: Skip clearing communication device"
227                     + "for invalid audio type (-1).");
228         }
229 
230         // There is only one audio device type associated with each type of BT device.
231         boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType);
232         Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s",
233                 audioDeviceType, isBtDevice);
234 
235         if (audioDeviceType != mAudioDeviceType
236                 && !isUsbHeadsetType(audioDeviceType, mAudioDeviceType)) {
237             Log.i(this, "Unable to clear communication device of type(s), %s. "
238                             + "Device does not correspond to the locally requested device type.",
239                     audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
240                             ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
241                             AudioDeviceInfo.TYPE_USB_HEADSET)
242                             : audioDeviceType
243             );
244             return;
245         }
246 
247         if (mAudioManager == null) {
248             Log.i(this, "clearCommunicationDevice: mAudioManager is null");
249             return;
250         }
251 
252         // Clear device and reset locally saved device type.
253         mAudioManager.clearCommunicationDevice();
254         mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
255 
256         if (isBtDevice && mBtAudioDevice != null) {
257             // Signal that BT audio was lost for device.
258             mBluetoothRouteManager.onAudioLost(mBtAudioDevice);
259             mBtAudioDevice = null;
260         }
261     }
262 
isUsbHeadsetType(int audioDeviceType, int sourceType)263     private boolean isUsbHeadsetType(int audioDeviceType, int sourceType) {
264         return audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
265                 && sourceType == AudioDeviceInfo.TYPE_USB_HEADSET;
266     }
267 }
268