• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 package com.android.settingslib.bluetooth;
17 
18 import android.bluetooth.BluetoothDevice;
19 import android.bluetooth.BluetoothHearingAid;
20 import android.bluetooth.BluetoothProfile;
21 import android.util.Log;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Set;
28 
29 /**
30  * HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices.
31  */
32 public class HearingAidDeviceManager {
33     private static final String TAG = "HearingAidDeviceManager";
34     private static final boolean DEBUG = BluetoothUtils.D;
35 
36     private final LocalBluetoothManager mBtManager;
37     private final List<CachedBluetoothDevice> mCachedDevices;
HearingAidDeviceManager(LocalBluetoothManager localBtManager, List<CachedBluetoothDevice> CachedDevices)38     HearingAidDeviceManager(LocalBluetoothManager localBtManager,
39             List<CachedBluetoothDevice> CachedDevices) {
40         mBtManager = localBtManager;
41         mCachedDevices = CachedDevices;
42     }
43 
initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice)44     void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
45         long hiSyncId = getHiSyncId(newDevice.getDevice());
46         if (isValidHiSyncId(hiSyncId)) {
47             // Once hiSyncId is valid, assign hiSyncId
48             newDevice.setHiSyncId(hiSyncId);
49             final int side = getDeviceSide(newDevice.getDevice());
50             final int mode = getDeviceMode(newDevice.getDevice());
51             newDevice.setDeviceSide(side);
52             newDevice.setDeviceMode(mode);
53         }
54     }
55 
getHiSyncId(BluetoothDevice device)56     private long getHiSyncId(BluetoothDevice device) {
57         final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
58         final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
59         if (profileProxy == null) {
60             return BluetoothHearingAid.HI_SYNC_ID_INVALID;
61         }
62 
63         return profileProxy.getHiSyncId(device);
64     }
65 
getDeviceSide(BluetoothDevice device)66     private int getDeviceSide(BluetoothDevice device) {
67         final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
68         final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
69         if (profileProxy == null) {
70             Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device side");
71             return HearingAidProfile.DeviceSide.SIDE_INVALID;
72         }
73 
74         return profileProxy.getDeviceSide(device);
75     }
76 
getDeviceMode(BluetoothDevice device)77     private int getDeviceMode(BluetoothDevice device) {
78         final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
79         final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
80         if (profileProxy == null) {
81             Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device mode");
82             return HearingAidProfile.DeviceMode.MODE_INVALID;
83         }
84 
85         return profileProxy.getDeviceMode(device);
86     }
87 
setSubDeviceIfNeeded(CachedBluetoothDevice newDevice)88     boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) {
89         final long hiSyncId = newDevice.getHiSyncId();
90         if (isValidHiSyncId(hiSyncId)) {
91             final CachedBluetoothDevice hearingAidDevice = getCachedDevice(hiSyncId);
92             // Just add one of the hearing aids from a pair in the list that is shown in the UI.
93             // Once there is another device with the same hiSyncId, to add new device as sub
94             // device.
95             if (hearingAidDevice != null) {
96                 hearingAidDevice.setSubDevice(newDevice);
97                 return true;
98             }
99         }
100         return false;
101     }
102 
isValidHiSyncId(long hiSyncId)103     private boolean isValidHiSyncId(long hiSyncId) {
104         return hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID;
105     }
106 
getCachedDevice(long hiSyncId)107     private CachedBluetoothDevice getCachedDevice(long hiSyncId) {
108         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
109             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
110             if (cachedDevice.getHiSyncId() == hiSyncId) {
111                 return cachedDevice;
112             }
113         }
114         return null;
115     }
116 
117     // To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId
updateHearingAidsDevices()118     void updateHearingAidsDevices() {
119         final Set<Long> newSyncIdSet = new HashSet<Long>();
120         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
121             // Do nothing if HiSyncId has been assigned
122             if (!isValidHiSyncId(cachedDevice.getHiSyncId())) {
123                 final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
124                 // Do nothing if there is no HiSyncId on Bluetooth device
125                 if (isValidHiSyncId(newHiSyncId)) {
126                     cachedDevice.setHiSyncId(newHiSyncId);
127                     newSyncIdSet.add(newHiSyncId);
128                     final int side = getDeviceSide(cachedDevice.getDevice());
129                     final int mode = getDeviceMode(cachedDevice.getDevice());
130                     cachedDevice.setDeviceSide(side);
131                     cachedDevice.setDeviceMode(mode);
132                 }
133             }
134         }
135         for (Long syncId : newSyncIdSet) {
136             onHiSyncIdChanged(syncId);
137         }
138     }
139 
140     // Group devices by hiSyncId
141     @VisibleForTesting
onHiSyncIdChanged(long hiSyncId)142     void onHiSyncIdChanged(long hiSyncId) {
143         int firstMatchedIndex = -1;
144 
145         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
146             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
147             if (cachedDevice.getHiSyncId() != hiSyncId) {
148                 continue;
149             }
150             if (firstMatchedIndex == -1) {
151                 // Found the first one
152                 firstMatchedIndex = i;
153                 continue;
154             }
155             // Found the second one
156             int indexToRemoveFromUi;
157             CachedBluetoothDevice subDevice;
158             CachedBluetoothDevice mainDevice;
159             // Since the hiSyncIds have been updated for a connected pair of hearing aids,
160             // we remove the entry of one the hearing aids from the UI. Unless the
161             // hiSyncId get updated, the system does not know it is a hearing aid, so we add
162             // both the hearing aids as separate entries in the UI first, then remove one
163             // of them after the hiSyncId is populated. We will choose the device that
164             // is not connected to be removed.
165             if (cachedDevice.isConnected()) {
166                 mainDevice = cachedDevice;
167                 indexToRemoveFromUi = firstMatchedIndex;
168                 subDevice = mCachedDevices.get(firstMatchedIndex);
169             } else {
170                 mainDevice = mCachedDevices.get(firstMatchedIndex);
171                 indexToRemoveFromUi = i;
172                 subDevice = cachedDevice;
173             }
174 
175             mainDevice.setSubDevice(subDevice);
176             mCachedDevices.remove(indexToRemoveFromUi);
177             log("onHiSyncIdChanged: removed from UI device =" + subDevice
178                     + ", with hiSyncId=" + hiSyncId);
179             mBtManager.getEventManager().dispatchDeviceRemoved(subDevice);
180             break;
181         }
182     }
183 
184     // @return {@code true}, the event is processed inside the method. It is for updating
185     // hearing aid device on main-sub relationship when receiving connected or disconnected.
186     // @return {@code false}, it is not hearing aid device or to process it same as other profiles
onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state)187     boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice,
188             int state) {
189         switch (state) {
190             case BluetoothProfile.STATE_CONNECTED:
191                 onHiSyncIdChanged(cachedDevice.getHiSyncId());
192                 CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice);
193                 if (mainDevice != null){
194                     if (mainDevice.isConnected()) {
195                         // When main device exists and in connected state, receiving sub device
196                         // connection. To refresh main device UI
197                         mainDevice.refresh();
198                         return true;
199                     } else {
200                         // When both Hearing Aid devices are disconnected, receiving sub device
201                         // connection. To switch content and dispatch to notify UI change
202                         mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice);
203                         mainDevice.switchSubDeviceContent();
204                         mainDevice.refresh();
205                         // It is necessary to do remove and add for updating the mapping on
206                         // preference and device
207                         mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
208                         return true;
209                     }
210                 }
211                 break;
212             case BluetoothProfile.STATE_DISCONNECTED:
213                 mainDevice = findMainDevice(cachedDevice);
214                 if (cachedDevice.getUnpairing()) {
215                     return true;
216                 }
217                 if (mainDevice != null) {
218                     // When main device exists, receiving sub device disconnection
219                     // To update main device UI
220                     mainDevice.refresh();
221                     return true;
222                 }
223                 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
224                 if (subDevice != null && subDevice.isConnected()) {
225                     // Main device is disconnected and sub device is connected
226                     // To copy data from sub device to main device
227                     mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
228                     cachedDevice.switchSubDeviceContent();
229                     cachedDevice.refresh();
230                     // It is necessary to do remove and add for updating the mapping on
231                     // preference and device
232                     mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
233                     return true;
234                 }
235                 break;
236         }
237         return false;
238     }
239 
findMainDevice(CachedBluetoothDevice device)240     CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) {
241         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
242             if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
243                 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
244                 if (subDevice != null && subDevice.equals(device)) {
245                     return cachedDevice;
246                 }
247             }
248         }
249         return null;
250     }
251 
log(String msg)252     private void log(String msg) {
253         if (DEBUG) {
254             Log.d(TAG, msg);
255         }
256     }
257 }