• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.bluetooth;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothHeadset;
21 import android.bluetooth.BluetoothHearingAid;
22 import android.bluetooth.BluetoothProfile;
23 import android.content.Context;
24 import android.telecom.Log;
25 
26 import com.android.server.telecom.BluetoothAdapterProxy;
27 import com.android.server.telecom.BluetoothHeadsetProxy;
28 
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.LinkedHashMap;
33 import java.util.LinkedHashSet;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Set;
37 
38 public class BluetoothDeviceManager {
39     private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
40             new BluetoothProfile.ServiceListener() {
41                 @Override
42                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
43                     Log.startSession("BMSL.oSC");
44                     try {
45                         synchronized (mLock) {
46                             if (profile == BluetoothProfile.HEADSET) {
47                                 mBluetoothHeadsetService =
48                                         new BluetoothHeadsetProxy((BluetoothHeadset) proxy);
49                                 Log.i(this, "- Got BluetoothHeadset: " + mBluetoothHeadsetService);
50                             } else if (profile == BluetoothProfile.HEARING_AID) {
51                                 mBluetoothHearingAidService = (BluetoothHearingAid) proxy;
52                                 Log.i(this, "- Got BluetoothHearingAid: "
53                                         + mBluetoothHearingAidService);
54                             } else {
55                                 Log.w(this, "Connected to non-requested bluetooth service." +
56                                         " Not changing bluetooth headset.");
57                             }
58                         }
59                     } finally {
60                         Log.endSession();
61                     }
62                 }
63 
64                 @Override
65                 public void onServiceDisconnected(int profile) {
66                     Log.startSession("BMSL.oSD");
67                     try {
68                         synchronized (mLock) {
69                             LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
70                             if (profile == BluetoothProfile.HEADSET) {
71                                 mBluetoothHeadsetService = null;
72                                 Log.i(BluetoothDeviceManager.this,
73                                         "Lost BluetoothHeadset service. " +
74                                                 "Removing all tracked devices.");
75                                 lostServiceDevices = mHfpDevicesByAddress;
76                                 mBluetoothRouteManager.onActiveDeviceChanged(null, false);
77                             } else if (profile == BluetoothProfile.HEARING_AID) {
78                                 mBluetoothHearingAidService = null;
79                                 Log.i(BluetoothDeviceManager.this,
80                                         "Lost BluetoothHearingAid service. " +
81                                                 "Removing all tracked devices.");
82                                 lostServiceDevices = mHearingAidDevicesByAddress;
83                                 mBluetoothRouteManager.onActiveDeviceChanged(null, true);
84                             } else {
85                                 return;
86                             }
87                             List<BluetoothDevice> devicesToRemove = new LinkedList<>(
88                                     lostServiceDevices.values());
89                             lostServiceDevices.clear();
90                             for (BluetoothDevice device : devicesToRemove) {
91                                 mBluetoothRouteManager.onDeviceLost(device.getAddress());
92                             }
93                         }
94                     } finally {
95                         Log.endSession();
96                     }
97                 }
98            };
99 
100     private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress =
101             new LinkedHashMap<>();
102     private final LinkedHashMap<String, BluetoothDevice> mHearingAidDevicesByAddress =
103             new LinkedHashMap<>();
104     private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds =
105             new LinkedHashMap<>();
106 
107     // This lock only protects internal state -- it doesn't lock on anything going into Telecom.
108     private final Object mLock = new Object();
109 
110     private BluetoothRouteManager mBluetoothRouteManager;
111     private BluetoothHeadsetProxy mBluetoothHeadsetService;
112     private BluetoothHearingAid mBluetoothHearingAidService;
113     private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
114 
BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter)115     public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter) {
116         if (bluetoothAdapter != null) {
117             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
118                     BluetoothProfile.HEADSET);
119             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
120                     BluetoothProfile.HEARING_AID);
121         }
122     }
123 
setBluetoothRouteManager(BluetoothRouteManager brm)124     public void setBluetoothRouteManager(BluetoothRouteManager brm) {
125         mBluetoothRouteManager = brm;
126     }
127 
getNumConnectedDevices()128     public int getNumConnectedDevices() {
129         synchronized (mLock) {
130             return mHfpDevicesByAddress.size() + mHearingAidDevicesByAddress.size();
131         }
132     }
133 
getConnectedDevices()134     public Collection<BluetoothDevice> getConnectedDevices() {
135         synchronized (mLock) {
136             ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
137             result.addAll(mHearingAidDevicesByAddress.values());
138             return Collections.unmodifiableCollection(result);
139         }
140     }
141 
142     // Same as getConnectedDevices except it filters out the hearing aid devices that are linked
143     // together by their hiSyncId.
getUniqueConnectedDevices()144     public Collection<BluetoothDevice> getUniqueConnectedDevices() {
145         ArrayList<BluetoothDevice> result;
146         synchronized (mLock) {
147             result = new ArrayList<>(mHfpDevicesByAddress.values());
148         }
149         Set<Long> seenHiSyncIds = new LinkedHashSet<>();
150         // Add the left-most active device to the seen list so that we match up with the list
151         // generated in BluetoothRouteManager.
152         if (mBluetoothHearingAidService != null) {
153             for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
154                 if (device != null) {
155                     result.add(device);
156                     seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L));
157                     break;
158                 }
159             }
160         }
161         synchronized (mLock) {
162             for (BluetoothDevice d : mHearingAidDevicesByAddress.values()) {
163                 long hiSyncId = mHearingAidDeviceSyncIds.getOrDefault(d, -1L);
164                 if (seenHiSyncIds.contains(hiSyncId)) {
165                     continue;
166                 }
167                 result.add(d);
168                 seenHiSyncIds.add(hiSyncId);
169             }
170         }
171         return Collections.unmodifiableCollection(result);
172     }
173 
getHeadsetService()174     public BluetoothHeadsetProxy getHeadsetService() {
175         return mBluetoothHeadsetService;
176     }
177 
getHearingAidService()178     public BluetoothHearingAid getHearingAidService() {
179         return mBluetoothHearingAidService;
180     }
181 
setHeadsetServiceForTesting(BluetoothHeadsetProxy bluetoothHeadset)182     public void setHeadsetServiceForTesting(BluetoothHeadsetProxy bluetoothHeadset) {
183         mBluetoothHeadsetService = bluetoothHeadset;
184     }
185 
setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid)186     public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) {
187         mBluetoothHearingAidService = bluetoothHearingAid;
188     }
189 
onDeviceConnected(BluetoothDevice device, boolean isHearingAid)190     void onDeviceConnected(BluetoothDevice device, boolean isHearingAid) {
191         synchronized (mLock) {
192             LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
193             if (isHearingAid) {
194                 if (mBluetoothHearingAidService == null) {
195                     Log.w(this, "Hearing aid service null when receiving device added broadcast");
196                     return;
197                 }
198                 long hiSyncId = mBluetoothHearingAidService.getHiSyncId(device);
199                 mHearingAidDeviceSyncIds.put(device, hiSyncId);
200                 targetDeviceMap = mHearingAidDevicesByAddress;
201             } else {
202                 if (mBluetoothHeadsetService == null) {
203                     Log.w(this, "Headset service null when receiving device added broadcast");
204                     return;
205                 }
206                 targetDeviceMap = mHfpDevicesByAddress;
207             }
208             if (!targetDeviceMap.containsKey(device.getAddress())) {
209                 targetDeviceMap.put(device.getAddress(), device);
210                 mBluetoothRouteManager.onDeviceAdded(device.getAddress());
211             }
212         }
213     }
214 
onDeviceDisconnected(BluetoothDevice device, boolean isHearingAid)215     void onDeviceDisconnected(BluetoothDevice device, boolean isHearingAid) {
216         synchronized (mLock) {
217             LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
218             if (isHearingAid) {
219                 mHearingAidDeviceSyncIds.remove(device);
220                 targetDeviceMap = mHearingAidDevicesByAddress;
221             } else {
222                 targetDeviceMap = mHfpDevicesByAddress;
223             }
224             if (targetDeviceMap.containsKey(device.getAddress())) {
225                 targetDeviceMap.remove(device.getAddress());
226                 mBluetoothRouteManager.onDeviceLost(device.getAddress());
227             }
228         }
229     }
230 
disconnectAudio()231     public void disconnectAudio() {
232         if (mBluetoothHearingAidService == null) {
233             Log.w(this, "Trying to disconnect audio but no hearing aid service exists");
234         } else {
235             for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
236                 if (device != null) {
237                     mBluetoothHearingAidService.setActiveDevice(null);
238                 }
239             }
240         }
241         disconnectSco();
242     }
243 
disconnectSco()244     public void disconnectSco() {
245         if (mBluetoothHeadsetService == null) {
246             Log.w(this, "Trying to disconnect audio but no headset service exists.");
247         } else {
248             mBluetoothHeadsetService.disconnectAudio();
249         }
250     }
251 
252     // Connect audio to the bluetooth device at address, checking to see whether it's a hearing aid
253     // or a HFP device, and using the proper BT API.
connectAudio(String address)254     public boolean connectAudio(String address) {
255         if (mHearingAidDevicesByAddress.containsKey(address)) {
256             if (mBluetoothHearingAidService == null) {
257                 Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
258                 return false;
259             }
260             return mBluetoothHearingAidService.setActiveDevice(
261                     mHearingAidDevicesByAddress.get(address));
262         } else if (mHfpDevicesByAddress.containsKey(address)) {
263             BluetoothDevice device = mHfpDevicesByAddress.get(address);
264             if (mBluetoothHeadsetService == null) {
265                 Log.w(this, "Attempting to turn on audio when the headset service is null");
266                 return false;
267             }
268             boolean success = mBluetoothHeadsetService.setActiveDevice(device);
269             if (!success) {
270                 Log.w(this, "Couldn't set active device to %s", address);
271                 return false;
272             }
273             if (!mBluetoothHeadsetService.isAudioOn()) {
274                 return mBluetoothHeadsetService.connectAudio();
275             }
276             return true;
277         } else {
278             Log.w(this, "Attempting to turn on audio for a disconnected device");
279             return false;
280         }
281     }
282 
cacheHearingAidDevice()283     public void cacheHearingAidDevice() {
284         if (mBluetoothHearingAidService != null) {
285              for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
286                  if (device != null) {
287                      mBluetoothHearingAidActiveDeviceCache = device;
288                  }
289              }
290         }
291     }
292 
restoreHearingAidDevice()293     public void restoreHearingAidDevice() {
294         if (mBluetoothHearingAidActiveDeviceCache != null && mBluetoothHearingAidService != null) {
295             mBluetoothHearingAidService.setActiveDevice(mBluetoothHearingAidActiveDeviceCache);
296             mBluetoothHearingAidActiveDeviceCache = null;
297         }
298     }
299 
300 }
301