1 /* 2 * Copyright (C) 2017 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.bluetooth.btservice; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothHeadset; 23 import android.bluetooth.BluetoothProfile; 24 import android.bluetooth.BluetoothSap; 25 import android.bluetooth.BluetoothUuid; 26 import android.bluetooth.IBluetooth; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.Parcelable; 35 import android.os.ParcelUuid; 36 import android.util.Log; 37 38 import com.android.bluetooth.a2dp.A2dpService; 39 import com.android.bluetooth.hid.HidService; 40 import com.android.bluetooth.hfp.HeadsetService; 41 import com.android.bluetooth.pan.PanService; 42 import com.android.internal.R; 43 44 import java.util.HashSet; 45 import java.util.List; 46 47 // Describes the phone policy 48 // 49 // The policy should be as decoupled from the stack as possible. In an ideal world we should not 50 // need to have this policy talk with any non-public APIs and one way to enforce that would be to 51 // keep this file outside the Bluetooth process. Unfortunately, keeping a separate process alive is 52 // an expensive and a tedious task. 53 // 54 // Best practices: 55 // a) PhonePolicy should be ALL private methods 56 // -- Use broadcasts which can be listened in on the BroadcastReceiver 57 // b) NEVER call from the PhonePolicy into the Java stack, unless public APIs. It is OK to call into 58 // the non public versions as long as public versions exist (so that a 3rd party policy can mimick) 59 // us. 60 // 61 // Policy description: 62 // 63 // Policies are usually governed by outside events that may warrant an action. We talk about various 64 // events and the resulting outcome from this policy: 65 // 66 // 1. Adapter turned ON: At this point we will try to auto-connect the (device, profile) pairs which 67 // have PRIORITY_AUTO_CONNECT. The fact that we *only* auto-connect Headset and A2DP is something 68 // that is hardcoded and specific to phone policy (see autoConnect() function) 69 // 2. When the profile connection-state changes: At this point if a new profile gets CONNECTED we 70 // will try to connect other profiles on the same device. This is to avoid collision if devices 71 // somehow end up trying to connect at same time or general connection issues. 72 class PhonePolicy { 73 final private static boolean DBG = true; 74 final private static String TAG = "BluetoothPhonePolicy"; 75 76 // Message types for the handler (internal messages generated by intents or timeouts) 77 final private static int MESSAGE_PROFILE_CONNECTION_STATE_CHANGED = 1; 78 final private static int MESSAGE_PROFILE_INIT_PRIORITIES = 2; 79 final private static int MESSAGE_CONNECT_OTHER_PROFILES = 3; 80 final private static int MESSAGE_ADAPTER_STATE_TURNED_ON = 4; 81 82 // Timeouts 83 final private static int CONNECT_OTHER_PROFILES_TIMEOUT = 6000; // 6s 84 85 final private AdapterService mAdapterService; 86 final private ServiceFactory mFactory; 87 final private Handler mHandler; 88 final private HashSet<BluetoothDevice> mHeadsetRetrySet = new HashSet<>(); 89 final private HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>(); 90 91 // Broadcast receiver for all changes to states of various profiles 92 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 93 @Override 94 public void onReceive(Context context, Intent intent) { 95 String action = intent.getAction(); 96 if (action == null) { 97 errorLog("Received intent with null action"); 98 return; 99 } 100 switch (action) { 101 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 102 mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED, 103 BluetoothProfile.HEADSET, 104 -1, // No-op argument 105 intent) 106 .sendToTarget(); 107 break; 108 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 109 mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED, 110 BluetoothProfile.A2DP, 111 -1, // No-op argument 112 intent) 113 .sendToTarget(); 114 break; 115 case BluetoothAdapter.ACTION_STATE_CHANGED: 116 // Only pass the message on if the adapter has actually changed state from 117 // non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON. 118 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 119 if (newState == BluetoothAdapter.STATE_ON) { 120 mHandler.obtainMessage(MESSAGE_ADAPTER_STATE_TURNED_ON).sendToTarget(); 121 } 122 break; 123 case BluetoothDevice.ACTION_UUID: 124 mHandler.obtainMessage(MESSAGE_PROFILE_INIT_PRIORITIES, intent).sendToTarget(); 125 break; 126 default: 127 Log.e(TAG, "Received unexpected intent, action=" + action); 128 break; 129 } 130 } 131 }; 132 133 // ONLY for testing 134 public BroadcastReceiver getBroadcastReceiver() { 135 return mReceiver; 136 } 137 138 // Handler to handoff intents to class thread 139 class PhonePolicyHandler extends Handler { 140 PhonePolicyHandler(Looper looper) { 141 super(looper); 142 } 143 144 @Override 145 public void handleMessage(Message msg) { 146 switch (msg.what) { 147 case MESSAGE_PROFILE_INIT_PRIORITIES: { 148 Intent intent = (Intent) msg.obj; 149 BluetoothDevice device = 150 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 151 Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); 152 debugLog("Received ACTION_UUID for device " + device); 153 if (uuids != null) { 154 ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length]; 155 for (int i = 0; i < uuidsToSend.length; i++) { 156 uuidsToSend[i] = (ParcelUuid) uuids[i]; 157 debugLog("index=" + i + "uuid=" + uuidsToSend[i]); 158 } 159 processInitProfilePriorities(device, uuidsToSend); 160 } 161 } break; 162 163 case MESSAGE_PROFILE_CONNECTION_STATE_CHANGED: { 164 Intent intent = (Intent) msg.obj; 165 BluetoothDevice device = 166 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 167 int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 168 int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 169 processProfileStateChanged(device, msg.arg1, nextState, prevState); 170 } break; 171 172 case MESSAGE_CONNECT_OTHER_PROFILES: 173 // Called when we try connect some profiles in processConnectOtherProfiles but 174 // we send a delayed message to try connecting the remaining profiles 175 processConnectOtherProfiles((BluetoothDevice) msg.obj); 176 break; 177 178 case MESSAGE_ADAPTER_STATE_TURNED_ON: 179 // Call auto connect when adapter switches state to ON 180 resetStates(); 181 autoConnect(); 182 break; 183 } 184 } 185 }; 186 187 // Policy API functions for lifecycle management (protected) 188 protected void start() { 189 IntentFilter filter = new IntentFilter(); 190 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 191 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 192 filter.addAction(BluetoothDevice.ACTION_UUID); 193 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 194 mAdapterService.registerReceiver(mReceiver, filter); 195 } 196 protected void cleanup() { 197 mAdapterService.unregisterReceiver(mReceiver); 198 resetStates(); 199 } 200 201 PhonePolicy(AdapterService service, ServiceFactory factory) { 202 mAdapterService = service; 203 mFactory = factory; 204 mHandler = new PhonePolicyHandler(service.getMainLooper()); 205 } 206 207 // Policy implementation, all functions MUST be private 208 private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) { 209 debugLog("processInitProfilePriorities() - device " + device); 210 HidService hidService = mFactory.getHidService(); 211 A2dpService a2dpService = mFactory.getA2dpService(); 212 HeadsetService headsetService = mFactory.getHeadsetService(); 213 PanService panService = mFactory.getPanService(); 214 215 // Set profile priorities only for the profiles discovered on the remote device. 216 // This avoids needless auto-connect attempts to profiles non-existent on the remote device 217 if ((hidService != null) 218 && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) 219 || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) 220 && (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) { 221 hidService.setPriority(device, BluetoothProfile.PRIORITY_ON); 222 } 223 224 // If we do not have a stored priority for HFP/A2DP (all roles) then default to on. 225 if ((headsetService != null) 226 && ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP) 227 || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) 228 && (headsetService.getPriority(device) 229 == BluetoothProfile.PRIORITY_UNDEFINED))) { 230 headsetService.setPriority(device, BluetoothProfile.PRIORITY_ON); 231 } 232 233 if ((a2dpService != null) 234 && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink) 235 || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) 236 && (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) { 237 a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON); 238 } 239 240 if ((panService != null) 241 && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU) 242 && (panService.getPriority(device) 243 == BluetoothProfile.PRIORITY_UNDEFINED) 244 && mAdapterService.getResources().getBoolean( 245 R.bool.config_bluetooth_pan_enable_autoconnect))) { 246 panService.setPriority(device, BluetoothProfile.PRIORITY_ON); 247 } 248 } 249 250 private void processProfileStateChanged( 251 BluetoothDevice device, int profileId, int nextState, int prevState) { 252 debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", " 253 + prevState + " -> " + nextState); 254 if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)) 255 && (nextState == BluetoothProfile.STATE_CONNECTED)) { 256 switch (profileId) { 257 case BluetoothProfile.A2DP: 258 mA2dpRetrySet.remove(device); 259 break; 260 case BluetoothProfile.HEADSET: 261 mHeadsetRetrySet.remove(device); 262 break; 263 } 264 connectOtherProfile(device); 265 setProfileAutoConnectionPriority(device, profileId); 266 } 267 } 268 269 private void resetStates() { 270 mHeadsetRetrySet.clear(); 271 mA2dpRetrySet.clear(); 272 } 273 274 private void autoConnect() { 275 if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { 276 errorLog("autoConnect() - BT is not ON. Exiting autoConnect"); 277 return; 278 } 279 280 if (!mAdapterService.isQuietModeEnabled()) { 281 debugLog("autoConnect() - Initiate auto connection on BT on..."); 282 // Phone profiles. 283 autoConnectHeadset(); 284 autoConnectA2dp(); 285 } else { 286 debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections"); 287 } 288 } 289 290 private void autoConnectHeadset() { 291 final HeadsetService hsService = mFactory.getHeadsetService(); 292 if (hsService == null) { 293 errorLog("autoConnectHeadset, service is null"); 294 return; 295 } 296 final BluetoothDevice bondedDevices[] = mAdapterService.getBondedDevices(); 297 if (bondedDevices == null) { 298 errorLog("autoConnectHeadset, bondedDevices are null"); 299 return; 300 } 301 for (BluetoothDevice device : bondedDevices) { 302 debugLog("autoConnectHeadset, attempt auto-connect with device " + device); 303 if (hsService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { 304 debugLog("autoConnectHeadset, Connecting HFP with " + device); 305 hsService.connect(device); 306 } 307 } 308 } 309 310 private void autoConnectA2dp() { 311 final A2dpService a2dpService = mFactory.getA2dpService(); 312 if (a2dpService == null) { 313 errorLog("autoConnectA2dp, service is null"); 314 return; 315 } 316 final BluetoothDevice bondedDevices[] = mAdapterService.getBondedDevices(); 317 if (bondedDevices == null) { 318 errorLog("autoConnectA2dp, bondedDevices are null"); 319 return; 320 } 321 for (BluetoothDevice device : bondedDevices) { 322 debugLog("autoConnectA2dp, attempt auto-connect with device " + device); 323 if (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { 324 debugLog("autoConnectA2dp, connecting A2DP with " + device); 325 a2dpService.connect(device); 326 } 327 } 328 } 329 330 private void connectOtherProfile(BluetoothDevice device) { 331 if ((!mHandler.hasMessages(MESSAGE_CONNECT_OTHER_PROFILES)) 332 && (!mAdapterService.isQuietModeEnabled())) { 333 Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES); 334 m.obj = device; 335 mHandler.sendMessageDelayed(m, CONNECT_OTHER_PROFILES_TIMEOUT); 336 } 337 } 338 339 // This function is called whenever a profile is connected. This allows any other bluetooth 340 // profiles which are not already connected or in the process of connecting to attempt to 341 // connect to the device that initiated the connection. In the event that this function is 342 // invoked and there are no current bluetooth connections no new profiles will be connected. 343 private void processConnectOtherProfiles(BluetoothDevice device) { 344 debugLog("processConnectOtherProfiles, device=" + device); 345 if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { 346 warnLog("processConnectOtherProfiles, adapter is not ON " + mAdapterService.getState()); 347 return; 348 } 349 HeadsetService hsService = mFactory.getHeadsetService(); 350 A2dpService a2dpService = mFactory.getA2dpService(); 351 PanService panService = mFactory.getPanService(); 352 353 boolean allProfilesEmpty = true; 354 List<BluetoothDevice> a2dpConnDevList = null; 355 List<BluetoothDevice> hsConnDevList = null; 356 List<BluetoothDevice> panConnDevList = null; 357 358 if (hsService != null) { 359 hsConnDevList = hsService.getConnectedDevices(); 360 allProfilesEmpty = allProfilesEmpty && hsConnDevList.isEmpty(); 361 } 362 if (a2dpService != null) { 363 a2dpConnDevList = a2dpService.getConnectedDevices(); 364 allProfilesEmpty = allProfilesEmpty && a2dpConnDevList.isEmpty(); 365 } 366 if (panService != null) { 367 panConnDevList = panService.getConnectedDevices(); 368 allProfilesEmpty = allProfilesEmpty && panConnDevList.isEmpty(); 369 } 370 371 if (allProfilesEmpty) { 372 // considered as fully disconnected, don't bother connecting others. 373 debugLog("processConnectOtherProfiles, all profiles disconnected for " + device); 374 // reset retry status so that in the next round we can start retrying connections again 375 resetStates(); 376 return; 377 } 378 379 if (hsService != null) { 380 if (hsConnDevList.isEmpty() && !mHeadsetRetrySet.contains(device) 381 && (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) 382 && (hsService.getConnectionState(device) 383 == BluetoothProfile.STATE_DISCONNECTED)) { 384 debugLog("Retrying connection to Headset with device " + device); 385 mHeadsetRetrySet.add(device); 386 hsService.connect(device); 387 } 388 } 389 if (a2dpService != null) { 390 if (a2dpConnDevList.isEmpty() && !mA2dpRetrySet.contains(device) 391 && (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) 392 && (a2dpService.getConnectionState(device) 393 == BluetoothProfile.STATE_DISCONNECTED)) { 394 debugLog("Retrying connection to A2DP with device " + device); 395 mA2dpRetrySet.add(device); 396 a2dpService.connect(device); 397 } 398 } 399 if (panService != null) { 400 if (panConnDevList.isEmpty() 401 && (panService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) 402 && (panService.getConnectionState(device) 403 == BluetoothProfile.STATE_DISCONNECTED)) { 404 debugLog("Retrying connection to PAN with device " + device); 405 panService.connect(device); 406 } 407 } 408 } 409 410 private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId) { 411 switch (profileId) { 412 case BluetoothProfile.HEADSET: 413 HeadsetService hsService = mFactory.getHeadsetService(); 414 if ((hsService != null) 415 && (BluetoothProfile.PRIORITY_AUTO_CONNECT 416 != hsService.getPriority(device))) { 417 List<BluetoothDevice> deviceList = hsService.getConnectedDevices(); 418 adjustOtherHeadsetPriorities(hsService, deviceList); 419 hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT); 420 } 421 break; 422 423 case BluetoothProfile.A2DP: 424 A2dpService a2dpService = mFactory.getA2dpService(); 425 if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT 426 != a2dpService.getPriority(device))) { 427 adjustOtherSinkPriorities(a2dpService, device); 428 a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT); 429 } 430 break; 431 432 default: 433 Log.w(TAG, "Tried to set AutoConnect priority on invalid profile " + profileId); 434 break; 435 } 436 } 437 438 private void adjustOtherHeadsetPriorities( 439 HeadsetService hsService, List<BluetoothDevice> connectedDeviceList) { 440 for (BluetoothDevice device : mAdapterService.getBondedDevices()) { 441 if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT 442 && !connectedDeviceList.contains(device)) { 443 hsService.setPriority(device, BluetoothProfile.PRIORITY_ON); 444 } 445 } 446 } 447 448 private void adjustOtherSinkPriorities( 449 A2dpService a2dpService, BluetoothDevice connectedDevice) { 450 for (BluetoothDevice device : mAdapterService.getBondedDevices()) { 451 if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT 452 && !device.equals(connectedDevice)) { 453 a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON); 454 } 455 } 456 } 457 458 private static void debugLog(String msg) { 459 if (DBG) Log.d(TAG, msg); 460 } 461 462 private static void warnLog(String msg) { 463 Log.w(TAG, msg); 464 } 465 466 private static void errorLog(String msg) { 467 Log.e(TAG, msg); 468 } 469 } 470