1 /* 2 * Copyright 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.media; 17 18 import android.app.Notification; 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothProfile; 22 import android.content.Context; 23 import android.util.Log; 24 25 import com.android.settingslib.bluetooth.A2dpProfile; 26 import com.android.settingslib.bluetooth.BluetoothCallback; 27 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 28 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 29 import com.android.settingslib.bluetooth.HearingAidProfile; 30 import com.android.settingslib.bluetooth.LocalBluetoothManager; 31 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * BluetoothMediaManager provide interface to get Bluetooth device list. 38 */ 39 public class BluetoothMediaManager extends MediaManager implements BluetoothCallback, 40 LocalBluetoothProfileManager.ServiceListener { 41 42 private static final String TAG = "BluetoothMediaManager"; 43 44 private final DeviceAttributeChangeCallback mDeviceAttributeChangeCallback = 45 new DeviceAttributeChangeCallback(); 46 47 private LocalBluetoothManager mLocalBluetoothManager; 48 private LocalBluetoothProfileManager mProfileManager; 49 private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager; 50 51 private MediaDevice mLastAddedDevice; 52 private MediaDevice mLastRemovedDevice; 53 54 private boolean mIsA2dpProfileReady = false; 55 private boolean mIsHearingAidProfileReady = false; 56 57 BluetoothMediaManager(Context context, LocalBluetoothManager localBluetoothManager, 58 Notification notification) { 59 super(context, notification); 60 61 mLocalBluetoothManager = localBluetoothManager; 62 mProfileManager = mLocalBluetoothManager.getProfileManager(); 63 mCachedBluetoothDeviceManager = mLocalBluetoothManager.getCachedDeviceManager(); 64 } 65 66 @Override 67 public void startScan() { 68 mLocalBluetoothManager.getEventManager().registerCallback(this); 69 buildBluetoothDeviceList(); 70 dispatchDeviceListAdded(); 71 addServiceListenerIfNecessary(); 72 } 73 74 private void addServiceListenerIfNecessary() { 75 // The profile may not ready when calling startScan(). 76 // Device status are all disconnected since profiles are not ready to connected. 77 // In this case, we observe onServiceConnected() in LocalBluetoothProfileManager. 78 // When A2dpProfile or HearingAidProfile is connected will call buildBluetoothDeviceList() 79 // again to find the connected devices. 80 if (!mIsA2dpProfileReady || !mIsHearingAidProfileReady) { 81 mProfileManager.addServiceListener(this); 82 } 83 } 84 85 private void buildBluetoothDeviceList() { 86 mMediaDevices.clear(); 87 addConnectableA2dpDevices(); 88 addConnectableHearingAidDevices(); 89 } 90 91 private void addConnectableA2dpDevices() { 92 final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); 93 if (a2dpProfile == null) { 94 Log.w(TAG, "addConnectableA2dpDevices() a2dp profile is null!"); 95 return; 96 } 97 98 final List<BluetoothDevice> devices = a2dpProfile.getConnectableDevices(); 99 100 for (BluetoothDevice device : devices) { 101 final CachedBluetoothDevice cachedDevice = 102 mCachedBluetoothDeviceManager.findDevice(device); 103 104 if (cachedDevice == null) { 105 Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName()); 106 continue; 107 } 108 109 Log.d(TAG, "addConnectableA2dpDevices() device : " + cachedDevice.getName() 110 + ", is connected : " + cachedDevice.isConnected() 111 + ", is preferred : " + a2dpProfile.isPreferred(device)); 112 113 if (a2dpProfile.isPreferred(device) 114 && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) { 115 addMediaDevice(cachedDevice); 116 } 117 } 118 119 mIsA2dpProfileReady = a2dpProfile.isProfileReady(); 120 } 121 122 private void addConnectableHearingAidDevices() { 123 final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile(); 124 if (hapProfile == null) { 125 Log.w(TAG, "addConnectableHearingAidDevices() hap profile is null!"); 126 return; 127 } 128 129 final List<Long> devicesHiSyncIds = new ArrayList<>(); 130 final List<BluetoothDevice> devices = hapProfile.getConnectableDevices(); 131 132 for (BluetoothDevice device : devices) { 133 final CachedBluetoothDevice cachedDevice = 134 mCachedBluetoothDeviceManager.findDevice(device); 135 136 if (cachedDevice == null) { 137 Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName()); 138 continue; 139 } 140 141 Log.d(TAG, "addConnectableHearingAidDevices() device : " + cachedDevice.getName() 142 + ", is connected : " + cachedDevice.isConnected() 143 + ", is preferred : " + hapProfile.isPreferred(device)); 144 145 final long hiSyncId = hapProfile.getHiSyncId(device); 146 147 // device with same hiSyncId should not be shown in the UI. 148 // So do not add it into connectedDevices. 149 if (!devicesHiSyncIds.contains(hiSyncId) && hapProfile.isPreferred(device) 150 && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) { 151 devicesHiSyncIds.add(hiSyncId); 152 addMediaDevice(cachedDevice); 153 } 154 } 155 156 mIsHearingAidProfileReady = hapProfile.isProfileReady(); 157 } 158 159 private void addMediaDevice(CachedBluetoothDevice cachedDevice) { 160 MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice)); 161 if (mediaDevice == null) { 162 mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice); 163 cachedDevice.registerCallback(mDeviceAttributeChangeCallback); 164 mLastAddedDevice = mediaDevice; 165 mMediaDevices.add(mediaDevice); 166 } 167 } 168 169 @Override 170 public void stopScan() { 171 mLocalBluetoothManager.getEventManager().unregisterCallback(this); 172 unregisterDeviceAttributeChangeCallback(); 173 } 174 175 private void unregisterDeviceAttributeChangeCallback() { 176 for (MediaDevice device : mMediaDevices) { 177 ((BluetoothMediaDevice) device).getCachedDevice() 178 .unregisterCallback(mDeviceAttributeChangeCallback); 179 } 180 } 181 182 @Override 183 public void onBluetoothStateChanged(int bluetoothState) { 184 if (BluetoothAdapter.STATE_ON == bluetoothState) { 185 buildBluetoothDeviceList(); 186 dispatchDeviceListAdded(); 187 addServiceListenerIfNecessary(); 188 } else if (BluetoothAdapter.STATE_OFF == bluetoothState) { 189 final List<MediaDevice> removeDevicesList = new ArrayList<>(); 190 for (MediaDevice device : mMediaDevices) { 191 ((BluetoothMediaDevice) device).getCachedDevice() 192 .unregisterCallback(mDeviceAttributeChangeCallback); 193 removeDevicesList.add(device); 194 } 195 mMediaDevices.removeAll(removeDevicesList); 196 dispatchDeviceListRemoved(removeDevicesList); 197 } 198 } 199 200 @Override 201 public void onAudioModeChanged() { 202 dispatchDataChanged(); 203 } 204 205 @Override 206 public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { 207 if (isCachedDeviceConnected(cachedDevice)) { 208 addMediaDevice(cachedDevice); 209 dispatchDeviceAdded(cachedDevice); 210 } 211 } 212 213 private boolean isCachedDeviceConnected(CachedBluetoothDevice cachedDevice) { 214 final boolean isConnectedHearingAidDevice = cachedDevice.isConnectedHearingAidDevice(); 215 final boolean isConnectedA2dpDevice = cachedDevice.isConnectedA2dpDevice(); 216 Log.d(TAG, "isCachedDeviceConnected() cachedDevice : " + cachedDevice 217 + ", is hearing aid connected : " + isConnectedHearingAidDevice 218 + ", is a2dp connected : " + isConnectedA2dpDevice); 219 220 return isConnectedHearingAidDevice || isConnectedA2dpDevice; 221 } 222 223 private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { 224 if (mLastAddedDevice != null 225 && MediaDeviceUtils.getId(cachedDevice) == mLastAddedDevice.getId()) { 226 dispatchDeviceAdded(mLastAddedDevice); 227 } 228 } 229 230 @Override 231 public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { 232 if (!isCachedDeviceConnected(cachedDevice)) { 233 removeMediaDevice(cachedDevice); 234 dispatchDeviceRemoved(cachedDevice); 235 } 236 } 237 238 private void removeMediaDevice(CachedBluetoothDevice cachedDevice) { 239 final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice)); 240 if (mediaDevice != null) { 241 cachedDevice.unregisterCallback(mDeviceAttributeChangeCallback); 242 mLastRemovedDevice = mediaDevice; 243 mMediaDevices.remove(mediaDevice); 244 } 245 } 246 247 void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) { 248 if (mLastRemovedDevice != null 249 && MediaDeviceUtils.getId(cachedDevice) == mLastRemovedDevice.getId()) { 250 dispatchDeviceRemoved(mLastRemovedDevice); 251 } 252 } 253 254 @Override 255 public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, 256 int bluetoothProfile) { 257 Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice 258 + ", state: " + state + ", bluetoothProfile: " + bluetoothProfile); 259 260 updateMediaDeviceListIfNecessary(cachedDevice); 261 } 262 263 private void updateMediaDeviceListIfNecessary(CachedBluetoothDevice cachedDevice) { 264 if (BluetoothDevice.BOND_NONE == cachedDevice.getBondState()) { 265 removeMediaDevice(cachedDevice); 266 dispatchDeviceRemoved(cachedDevice); 267 } else { 268 if (findMediaDevice(MediaDeviceUtils.getId(cachedDevice)) != null) { 269 dispatchDataChanged(); 270 } 271 } 272 } 273 274 @Override 275 public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { 276 Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice + ", state: " + state); 277 278 updateMediaDeviceListIfNecessary(cachedDevice); 279 } 280 281 @Override 282 public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { 283 Log.d(TAG, "onActiveDeviceChanged : device : " 284 + activeDevice + ", profile : " + bluetoothProfile); 285 286 if (BluetoothProfile.HEARING_AID == bluetoothProfile) { 287 if (activeDevice != null) { 288 dispatchConnectedDeviceChanged(MediaDeviceUtils.getId(activeDevice)); 289 } 290 } else if (BluetoothProfile.A2DP == bluetoothProfile) { 291 // When active device change to Hearing Aid, 292 // BluetoothEventManager also send onActiveDeviceChanged() to notify that active device 293 // of A2DP profile is null. To handle this case, check hearing aid device 294 // is active device or not 295 final MediaDevice activeHearingAidDevice = findActiveHearingAidDevice(); 296 final String id = activeDevice == null 297 ? activeHearingAidDevice == null 298 ? PhoneMediaDevice.ID : activeHearingAidDevice.getId() 299 : MediaDeviceUtils.getId(activeDevice); 300 dispatchConnectedDeviceChanged(id); 301 } 302 } 303 304 private MediaDevice findActiveHearingAidDevice() { 305 final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); 306 307 if (hearingAidProfile != null) { 308 final List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices(); 309 for (BluetoothDevice btDevice : activeDevices) { 310 if (btDevice != null) { 311 return findMediaDevice(MediaDeviceUtils.getId(btDevice)); 312 } 313 } 314 } 315 return null; 316 } 317 318 @Override 319 public void onServiceConnected() { 320 if (!mIsA2dpProfileReady || !mIsHearingAidProfileReady) { 321 buildBluetoothDeviceList(); 322 dispatchDeviceListAdded(); 323 } 324 325 //Remove the listener once a2dpProfile and hearingAidProfile are ready. 326 if (mIsA2dpProfileReady && mIsHearingAidProfileReady) { 327 mProfileManager.removeServiceListener(this); 328 } 329 } 330 331 @Override 332 public void onServiceDisconnected() { 333 334 } 335 336 /** 337 * This callback is for update {@link BluetoothMediaDevice} summary when 338 * {@link CachedBluetoothDevice} connection state is changed. 339 */ 340 private class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback { 341 342 @Override 343 public void onDeviceAttributesChanged() { 344 dispatchDataChanged(); 345 } 346 } 347 } 348