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