1 /*
2  * Copyright (C) 2008 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.settingslib.bluetooth;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothDevice;
21 import android.content.Context;
22 import android.util.Log;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.List;
29 
30 /**
31  * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
32  */
33 public class CachedBluetoothDeviceManager {
34     private static final String TAG = "CachedBluetoothDeviceManager";
35     private static final boolean DEBUG = BluetoothUtils.D;
36 
37     private Context mContext;
38     private final LocalBluetoothManager mBtManager;
39 
40     @VisibleForTesting
41     final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>();
42     @VisibleForTesting
43     HearingAidDeviceManager mHearingAidDeviceManager;
44 
CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager)45     CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
46         mContext = context;
47         mBtManager = localBtManager;
48         mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
49     }
50 
getCachedDevicesCopy()51     public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
52         return new ArrayList<>(mCachedDevices);
53     }
54 
onDeviceDisappeared(CachedBluetoothDevice cachedDevice)55     public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
56         cachedDevice.setJustDiscovered(false);
57         return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
58     }
59 
onDeviceNameUpdated(BluetoothDevice device)60     public void onDeviceNameUpdated(BluetoothDevice device) {
61         CachedBluetoothDevice cachedDevice = findDevice(device);
62         if (cachedDevice != null) {
63             cachedDevice.refreshName();
64         }
65     }
66 
67     /**
68      * Search for existing {@link CachedBluetoothDevice} or return null
69      * if this device isn't in the cache. Use {@link #addDevice}
70      * to create and return a new {@link CachedBluetoothDevice} for
71      * a newly discovered {@link BluetoothDevice}.
72      *
73      * @param device the address of the Bluetooth device
74      * @return the cached device object for this device, or null if it has
75      *   not been previously seen
76      */
findDevice(BluetoothDevice device)77     public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) {
78         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
79             if (cachedDevice.getDevice().equals(device)) {
80                 return cachedDevice;
81             }
82             // Check sub devices if it exists
83             CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
84             if (subDevice != null && subDevice.getDevice().equals(device)) {
85                 return subDevice;
86             }
87         }
88 
89         return null;
90     }
91 
92     /**
93      * Create and return a new {@link CachedBluetoothDevice}. This assumes
94      * that {@link #findDevice} has already been called and returned null.
95      * @param device the address of the new Bluetooth device
96      * @return the newly created CachedBluetoothDevice object
97      */
addDevice(BluetoothDevice device)98     public CachedBluetoothDevice addDevice(BluetoothDevice device) {
99         CachedBluetoothDevice newDevice;
100         final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
101         synchronized (this) {
102             newDevice = findDevice(device);
103             if (newDevice == null) {
104                 newDevice = new CachedBluetoothDevice(mContext, profileManager, device);
105                 mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
106                 if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
107                     mCachedDevices.add(newDevice);
108                     mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
109                 }
110             }
111         }
112 
113         return newDevice;
114     }
115 
116     /**
117      * Returns device summary of the pair of the hearing aid passed as the parameter.
118      *
119      * @param CachedBluetoothDevice device
120      * @return Device summary, or if the pair does not exist or if it is not a hearing aid,
121      * then {@code null}.
122      */
getSubDeviceSummary(CachedBluetoothDevice device)123     public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) {
124         CachedBluetoothDevice subDevice = device.getSubDevice();
125         if (subDevice != null && subDevice.isConnected()) {
126             return subDevice.getConnectionSummary();
127         }
128         return null;
129     }
130 
131     /**
132      * Search for existing sub device {@link CachedBluetoothDevice}.
133      *
134      * @param device the address of the Bluetooth device
135      * @return true for found sub device or false.
136      */
isSubDevice(BluetoothDevice device)137     public synchronized boolean isSubDevice(BluetoothDevice device) {
138         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
139             if (!cachedDevice.getDevice().equals(device)) {
140                 // Check sub devices if it exists
141                 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
142                 if (subDevice != null && subDevice.getDevice().equals(device)) {
143                     return true;
144                 }
145             }
146         }
147         return false;
148     }
149 
150     /**
151      * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
152      * Hearing Aid Service is connected and the HiSyncId's are now available.
153      * @param LocalBluetoothProfileManager profileManager
154      */
updateHearingAidsDevices()155     public synchronized void updateHearingAidsDevices() {
156         mHearingAidDeviceManager.updateHearingAidsDevices();
157     }
158 
159     /**
160      * Attempts to get the name of a remote device, otherwise returns the address.
161      *
162      * @param device The remote device.
163      * @return The name, or if unavailable, the address.
164      */
getName(BluetoothDevice device)165     public String getName(BluetoothDevice device) {
166         CachedBluetoothDevice cachedDevice = findDevice(device);
167         if (cachedDevice != null && cachedDevice.getName() != null) {
168             return cachedDevice.getName();
169         }
170 
171         String name = device.getAlias();
172         if (name != null) {
173             return name;
174         }
175 
176         return device.getAddress();
177     }
178 
clearNonBondedDevices()179     public synchronized void clearNonBondedDevices() {
180         clearNonBondedSubDevices();
181         mCachedDevices.removeIf(cachedDevice
182             -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
183     }
184 
clearNonBondedSubDevices()185     private void clearNonBondedSubDevices() {
186         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
187             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
188             CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
189             if (subDevice != null
190                     && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
191                 // Sub device exists and it is not bonded
192                 cachedDevice.setSubDevice(null);
193             }
194         }
195     }
196 
onScanningStateChanged(boolean started)197     public synchronized void onScanningStateChanged(boolean started) {
198         if (!started) return;
199         // If starting a new scan, clear old visibility
200         // Iterate in reverse order since devices may be removed.
201         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
202             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
203             cachedDevice.setJustDiscovered(false);
204             final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
205             if (subDevice != null) {
206                 subDevice.setJustDiscovered(false);
207             }
208         }
209     }
210 
onBluetoothStateChanged(int bluetoothState)211     public synchronized void onBluetoothStateChanged(int bluetoothState) {
212         // When Bluetooth is turning off, we need to clear the non-bonded devices
213         // Otherwise, they end up showing up on the next BT enable
214         if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
215             for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
216                 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
217                 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
218                 if (subDevice != null) {
219                     if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
220                         cachedDevice.setSubDevice(null);
221                     }
222                 }
223                 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
224                     cachedDevice.setJustDiscovered(false);
225                     mCachedDevices.remove(i);
226                 }
227             }
228         }
229     }
230 
onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state)231     public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
232             cachedDevice, int state) {
233         return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
234                 state);
235     }
236 
onDeviceUnpaired(CachedBluetoothDevice device)237     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
238         CachedBluetoothDevice mainDevice = mHearingAidDeviceManager.findMainDevice(device);
239         CachedBluetoothDevice subDevice = device.getSubDevice();
240         if (subDevice != null) {
241             // Main device is unpaired, to unpair sub device
242             subDevice.unpair();
243             device.setSubDevice(null);
244         } else if (mainDevice != null) {
245             // Sub device unpaired, to unpair main device
246             mainDevice.unpair();
247             mainDevice.setSubDevice(null);
248         }
249     }
250 
log(String msg)251     private void log(String msg) {
252         if (DEBUG) {
253             Log.d(TAG, msg);
254         }
255     }
256 }
257