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