• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 @AudioDeviceInfo.AudioDeviceType 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(@udioDeviceInfo.AudioDeviceType int audioDeviceType)62     public boolean isAudioDeviceSetForType(@AudioDeviceInfo.AudioDeviceType 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(@udioDeviceInfo.AudioDeviceType int audioDeviceType)89     public void setTestCommunicationDevice(@AudioDeviceInfo.AudioDeviceType 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(@udioDeviceInfo.AudioDeviceType int audioDeviceType, BluetoothDevice btDevice)122     public boolean setCommunicationDevice(@AudioDeviceInfo.AudioDeviceType 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( @udioDeviceInfo.AudioDeviceType int audioDeviceType, BluetoothDevice btDevice)136     private boolean processSetCommunicationDevice(
137         @AudioDeviceInfo.AudioDeviceType int audioDeviceType, 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                 || isSpeakerType(audioDeviceType, mAudioDeviceType))
149                 && !handleLeAudioDeviceSwitch) {
150             Log.i(this, "Communication device is already set for this audio type");
151             return false;
152         }
153 
154         AudioDeviceInfo activeDevice = null;
155         List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
156         if (devices.size() == 0) {
157             Log.w(this, "No communication devices available");
158             return false;
159         }
160 
161         for (AudioDeviceInfo device : devices) {
162             Log.i(this, "Available device type: " + device.getType());
163             // Ensure that we do not select the same BT LE audio device for communication.
164             if ((audioDeviceType == device.getType()
165                     || isUsbHeadsetType(audioDeviceType, device.getType())
166                     || isSpeakerType(audioDeviceType, device.getType()))
167                     && !device.getAddress().equals(mBtAudioDevice)) {
168                 activeDevice = device;
169                 break;
170             }
171         }
172 
173         if (activeDevice == null) {
174             Log.i(this, "No active device of type(s) %s available",
175                     audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
176                             ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
177                             AudioDeviceInfo.TYPE_USB_HEADSET)
178                             : audioDeviceType);
179             return false;
180         }
181 
182         // Force clear previous communication device, if one was set, before setting the new device.
183         if (mAudioDeviceType != sAUDIO_DEVICE_TYPE_INVALID) {
184             processClearCommunicationDevice(mAudioDeviceType);
185         }
186 
187         // Turn activeDevice ON.
188         boolean result = mAudioManager.setCommunicationDevice(activeDevice);
189         if (!result) {
190             Log.w(this, "Could not set active device");
191         } else {
192             Log.i(this, "Active device set");
193             mAudioDeviceType = activeDevice.getType();
194             if (isBtDevice) {
195                 mBtAudioDevice = activeDevice.getAddress();
196                 if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) {
197                     mBluetoothRouteManager.onAudioOn(mBtAudioDevice);
198                 }
199             } else if (Flags.communicationDeviceProtectedByLock()) {
200                 // Clear BT device if it's still stored. Handles race condition for when a non-BT
201                 // device is set for communication shortly after a BT (LE) device is set for
202                 // communication but the selection hasn't been cleared yet.
203                 mBtAudioDevice = null;
204             }
205         }
206         return result;
207     }
208     /*
209      * Clears the communication device for the passed in audio device types, given that the device
210      * has previously been set for communication.
211      * @param audioDeviceTypes The supported audio device types for the device.
212      */
clearCommunicationDevice(@udioDeviceInfo.AudioDeviceType int audioDeviceType)213     public void clearCommunicationDevice(@AudioDeviceInfo.AudioDeviceType int audioDeviceType) {
214         if (Flags.communicationDeviceProtectedByLock()) {
215             mLock.lock();
216         }
217         try {
218             processClearCommunicationDevice(audioDeviceType);
219         } finally {
220             if (Flags.communicationDeviceProtectedByLock()) {
221                 mLock.unlock();
222             }
223         }
224     }
225 
processClearCommunicationDevice( @udioDeviceInfo.AudioDeviceType int audioDeviceType)226     public void processClearCommunicationDevice(
227         @AudioDeviceInfo.AudioDeviceType int audioDeviceType) {
228         if (audioDeviceType == sAUDIO_DEVICE_TYPE_INVALID) {
229             Log.i(this, "clearCommunicationDevice: Skip clearing communication device"
230                     + "for invalid audio type (-1).");
231         }
232 
233         // There is only one audio device type associated with each type of BT device.
234         boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType);
235         Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s",
236                 audioDeviceType, isBtDevice);
237 
238         if (audioDeviceType != mAudioDeviceType
239                 && !isUsbHeadsetType(audioDeviceType, mAudioDeviceType)
240                 && !isSpeakerType(audioDeviceType, mAudioDeviceType)) {
241             Log.i(this, "Unable to clear communication device of type(s) %s. "
242                             + "Device does not correspond to the locally requested device type %s.",
243                     audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
244                             ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
245                             AudioDeviceInfo.TYPE_USB_HEADSET)
246                             : audioDeviceType,
247                     mAudioDeviceType
248             );
249             return;
250         }
251 
252         if (mAudioManager == null) {
253             Log.i(this, "clearCommunicationDevice: mAudioManager is null");
254             return;
255         }
256 
257         // Clear device and reset locally saved device type.
258         Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice()");
259         mAudioManager.clearCommunicationDevice();
260         mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
261 
262         if (isBtDevice && mBtAudioDevice != null) {
263             // Signal that BT audio was lost for device.
264             mBluetoothRouteManager.onAudioLost(mBtAudioDevice);
265             mBtAudioDevice = null;
266         }
267     }
268 
isUsbHeadsetType(@udioDeviceInfo.AudioDeviceType int audioDeviceType, @AudioDeviceInfo.AudioDeviceType int sourceType)269     private boolean isUsbHeadsetType(@AudioDeviceInfo.AudioDeviceType int audioDeviceType,
270         @AudioDeviceInfo.AudioDeviceType int sourceType) {
271         return audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
272                 && sourceType == AudioDeviceInfo.TYPE_USB_HEADSET;
273     }
274 
isSpeakerType(@udioDeviceInfo.AudioDeviceType int audioDeviceType, @AudioDeviceInfo.AudioDeviceType int sourceType)275     private boolean isSpeakerType(@AudioDeviceInfo.AudioDeviceType int audioDeviceType,
276         @AudioDeviceInfo.AudioDeviceType int sourceType) {
277         if (!Flags.busDeviceIsASpeaker()) return false;
278         return audioDeviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
279                 && sourceType == AudioDeviceInfo.TYPE_BUS;
280     }
281 }
282