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