PhonePolicy.java revision d94f6ddab1f088340d197086949409c144ec2b0a
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.BluetoothSap; 25import android.bluetooth.BluetoothUuid; 26import android.bluetooth.IBluetooth; 27import android.content.BroadcastReceiver; 28import android.content.Context; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.os.Handler; 32import android.os.Looper; 33import android.os.Message; 34import android.os.Parcelable; 35import android.os.ParcelUuid; 36import android.util.Log; 37 38import com.android.bluetooth.a2dp.A2dpService; 39import com.android.bluetooth.hid.HidService; 40import com.android.bluetooth.hfp.HeadsetService; 41import com.android.bluetooth.pan.PanService; 42import com.android.internal.R; 43 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 final private static boolean DBG = true; 73 final private static String TAG = "BluetoothPhonePolicy"; 74 75 // Message types for the handler (internal messages generated by intents or timeouts) 76 final private static int MESSAGE_PROFILE_CONNECTION_STATE_CHANGED = 1; 77 final private static int MESSAGE_PROFILE_INIT_PRIORITIES = 2; 78 final private static int MESSAGE_CONNECT_OTHER_PROFILES = 3; 79 final private static int MESSAGE_ADAPTER_STATE_TURNED_ON = 4; 80 81 // Timeouts 82 final private static int CONNECT_OTHER_PROFILES_TIMEOUT = 6000; // 6s 83 84 final private AdapterService mAdapterService; 85 final private ServiceFactory mFactory; 86 final private Handler mHandler; 87 88 // Broadcast receiver for all changes to states of various profiles 89 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 90 @Override 91 public void onReceive(Context context, Intent intent) { 92 String action = intent.getAction(); 93 if (action == null) { 94 errorLog("Received intent with null action"); 95 return; 96 } 97 switch (action) { 98 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 99 mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED, 100 BluetoothProfile.HEADSET, 101 -1, // No-op argument 102 intent) 103 .sendToTarget(); 104 break; 105 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 106 mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED, 107 BluetoothProfile.A2DP, 108 -1, // No-op argument 109 intent) 110 .sendToTarget(); 111 break; 112 case BluetoothAdapter.ACTION_STATE_CHANGED: 113 // Only pass the message on if the adapter has actually changed state from 114 // non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON. 115 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 116 if (newState == BluetoothAdapter.STATE_ON) { 117 mHandler.obtainMessage(MESSAGE_ADAPTER_STATE_TURNED_ON).sendToTarget(); 118 } 119 break; 120 case BluetoothDevice.ACTION_UUID: 121 mHandler.obtainMessage(MESSAGE_PROFILE_INIT_PRIORITIES, intent).sendToTarget(); 122 break; 123 default: 124 Log.e(TAG, "Received unexpected intent, action=" + action); 125 break; 126 } 127 } 128 }; 129 130 // ONLY for testing 131 public BroadcastReceiver getBroadcastReceiver() { 132 return mReceiver; 133 } 134 135 // Handler to handoff intents to class thread 136 class PhonePolicyHandler extends Handler { 137 PhonePolicyHandler(Looper looper) { 138 super(looper); 139 } 140 141 @Override 142 public void handleMessage(Message msg) { 143 switch (msg.what) { 144 case MESSAGE_PROFILE_INIT_PRIORITIES: { 145 Intent intent = (Intent) msg.obj; 146 BluetoothDevice device = 147 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 148 Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); 149 debugLog("Received ACTION_UUID for device " + device); 150 if (uuids != null) { 151 ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length]; 152 for (int i = 0; i < uuidsToSend.length; i++) { 153 uuidsToSend[i] = (ParcelUuid) uuids[i]; 154 debugLog("index=" + i + "uuid=" + uuidsToSend[i]); 155 } 156 processInitProfilePriorities(device, uuidsToSend); 157 } 158 } break; 159 160 case MESSAGE_PROFILE_CONNECTION_STATE_CHANGED: { 161 Intent intent = (Intent) msg.obj; 162 BluetoothDevice device = 163 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 164 int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 165 int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 166 processProfileStateChanged(device, msg.arg1, nextState, prevState); 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 autoConnect(); 178 break; 179 } 180 } 181 }; 182 183 // Policy API functions for lifecycle management (protected) 184 protected void start() { 185 IntentFilter filter = new IntentFilter(); 186 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 187 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 188 filter.addAction(BluetoothDevice.ACTION_UUID); 189 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 190 mAdapterService.registerReceiver(mReceiver, filter); 191 } 192 protected void cleanup() { 193 mAdapterService.unregisterReceiver(mReceiver); 194 } 195 196 PhonePolicy(AdapterService service, ServiceFactory factory) { 197 mAdapterService = service; 198 mFactory = factory; 199 mHandler = new PhonePolicyHandler(service.getMainLooper()); 200 } 201 202 // Policy implementation, all functions MUST be private 203 private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) { 204 debugLog("processInitProfilePriorities() - device " + device); 205 HidService hidService = mFactory.getHidService(); 206 A2dpService a2dpService = mFactory.getA2dpService(); 207 HeadsetService headsetService = mFactory.getHeadsetService(); 208 PanService panService = mFactory.getPanService(); 209 210 // Set profile priorities only for the profiles discovered on the remote device. 211 // This avoids needless auto-connect attempts to profiles non-existent on the remote device 212 if ((hidService != null) 213 && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) 214 || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) 215 && (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) { 216 hidService.setPriority(device, BluetoothProfile.PRIORITY_ON); 217 } 218 219 // If we do not have a stored priority for HFP/A2DP (all roles) then default to on. 220 if ((headsetService != null) 221 && ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP) 222 || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) 223 && (headsetService.getPriority(device) 224 == BluetoothProfile.PRIORITY_UNDEFINED))) { 225 headsetService.setPriority(device, BluetoothProfile.PRIORITY_ON); 226 } 227 228 if ((a2dpService != null) 229 && (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) 236 && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU) 237 && (panService.getPriority(device) 238 == BluetoothProfile.PRIORITY_UNDEFINED) 239 && mAdapterService.getResources().getBoolean( 240 R.bool.config_bluetooth_pan_enable_autoconnect))) { 241 panService.setPriority(device, BluetoothProfile.PRIORITY_ON); 242 } 243 } 244 245 private void processProfileStateChanged( 246 BluetoothDevice device, int profileId, int nextState, int prevState) { 247 debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", " 248 + prevState + " -> " + nextState); 249 if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)) 250 && (nextState == BluetoothProfile.STATE_CONNECTED)) { 251 connectOtherProfile(device); 252 setProfileAutoConnectionPriority(device, profileId); 253 } 254 } 255 256 private void autoConnect() { 257 if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { 258 errorLog("autoConnect() - BT is not ON. Exiting autoConnect"); 259 return; 260 } 261 262 if (!mAdapterService.isQuietModeEnabled()) { 263 debugLog("autoConnect() - Initiate auto connection on BT on..."); 264 // Phone profiles. 265 autoConnectHeadset(); 266 autoConnectA2dp(); 267 } else { 268 debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections"); 269 } 270 } 271 272 private void autoConnectHeadset() { 273 final HeadsetService hsService = mFactory.getHeadsetService(); 274 if (hsService == null) { 275 errorLog("autoConnectHeadset, service is null"); 276 return; 277 } 278 final BluetoothDevice bondedDevices[] = mAdapterService.getBondedDevices(); 279 if (bondedDevices == null) { 280 errorLog("autoConnectHeadset, bondedDevices are null"); 281 return; 282 } 283 for (BluetoothDevice device : bondedDevices) { 284 debugLog("autoConnectHeadset, attempt auto-connect with device " + device); 285 if (hsService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { 286 debugLog("autoConnectHeadset, Connecting HFP with " + device); 287 hsService.connect(device); 288 } 289 } 290 } 291 292 private void autoConnectA2dp() { 293 final A2dpService a2dpService = mFactory.getA2dpService(); 294 if (a2dpService == null) { 295 errorLog("autoConnectA2dp, service is null"); 296 return; 297 } 298 final BluetoothDevice bondedDevices[] = mAdapterService.getBondedDevices(); 299 if (bondedDevices == null) { 300 errorLog("autoConnectA2dp, bondedDevices are null"); 301 return; 302 } 303 for (BluetoothDevice device : bondedDevices) { 304 debugLog("autoConnectA2dp, attempt auto-connect with device " + device); 305 if (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { 306 debugLog("autoConnectA2dp, connecting A2DP with " + device); 307 a2dpService.connect(device); 308 } 309 } 310 } 311 312 private void connectOtherProfile(BluetoothDevice device) { 313 if ((!mHandler.hasMessages(MESSAGE_CONNECT_OTHER_PROFILES)) 314 && (!mAdapterService.isQuietModeEnabled())) { 315 Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES); 316 m.obj = device; 317 mHandler.sendMessageDelayed(m, CONNECT_OTHER_PROFILES_TIMEOUT); 318 } 319 } 320 321 // This function is called whenever a profile is connected. This allows any other bluetooth 322 // profiles which are not already connected or in the process of connecting to attempt to 323 // connect to the device that initiated the connection. In the event that this function is 324 // invoked and there are no current bluetooth connections no new profiles will be connected. 325 private void processConnectOtherProfiles(BluetoothDevice device) { 326 debugLog("processConnectOtherProfiles, device=" + device); 327 if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { 328 warnLog("processConnectOtherProfiles, adapter is not ON " + mAdapterService.getState()); 329 return; 330 } 331 HeadsetService hsService = mFactory.getHeadsetService(); 332 A2dpService a2dpService = mFactory.getA2dpService(); 333 PanService panService = mFactory.getPanService(); 334 335 boolean allProfilesEmpty = true; 336 List<BluetoothDevice> a2dpConnDevList = null; 337 List<BluetoothDevice> hsConnDevList = null; 338 List<BluetoothDevice> panConnDevList = null; 339 340 if (hsService != null) { 341 hsConnDevList = hsService.getConnectedDevices(); 342 allProfilesEmpty = allProfilesEmpty && hsConnDevList.isEmpty(); 343 } 344 if (a2dpService != null) { 345 a2dpConnDevList = a2dpService.getConnectedDevices(); 346 allProfilesEmpty = allProfilesEmpty && a2dpConnDevList.isEmpty(); 347 } 348 if (panService != null) { 349 panConnDevList = panService.getConnectedDevices(); 350 allProfilesEmpty = allProfilesEmpty && panConnDevList.isEmpty(); 351 } 352 353 if (allProfilesEmpty) { 354 debugLog("processConnectOtherProfiles, all profiles disconnected for " + device); 355 // must have connected then disconnected, don't bother connecting others. 356 return; 357 } 358 359 if (hsService != null) { 360 if (hsConnDevList.isEmpty() 361 && (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) 362 && (hsService.getConnectionState(device) 363 == BluetoothProfile.STATE_DISCONNECTED)) { 364 debugLog("Retrying connection to Headset with device " + device); 365 hsService.connect(device); 366 } 367 } 368 if (a2dpService != null) { 369 if (a2dpConnDevList.isEmpty() 370 && (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) 371 && (a2dpService.getConnectionState(device) 372 == BluetoothProfile.STATE_DISCONNECTED)) { 373 debugLog("Retrying connection to A2DP with device " + device); 374 a2dpService.connect(device); 375 } 376 } 377 if (panService != null) { 378 if (panConnDevList.isEmpty() 379 && (panService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) 380 && (panService.getConnectionState(device) 381 == BluetoothProfile.STATE_DISCONNECTED)) { 382 debugLog("Retrying connection to PAN with device " + device); 383 panService.connect(device); 384 } 385 } 386 } 387 388 private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId) { 389 switch (profileId) { 390 case BluetoothProfile.HEADSET: 391 HeadsetService hsService = mFactory.getHeadsetService(); 392 if ((hsService != null) 393 && (BluetoothProfile.PRIORITY_AUTO_CONNECT 394 != hsService.getPriority(device))) { 395 List<BluetoothDevice> deviceList = hsService.getConnectedDevices(); 396 adjustOtherHeadsetPriorities(hsService, deviceList); 397 hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT); 398 } 399 break; 400 401 case BluetoothProfile.A2DP: 402 A2dpService a2dpService = mFactory.getA2dpService(); 403 if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT 404 != a2dpService.getPriority(device))) { 405 adjustOtherSinkPriorities(a2dpService, device); 406 a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT); 407 } 408 break; 409 410 default: 411 Log.w(TAG, "Tried to set AutoConnect priority on invalid profile " + profileId); 412 break; 413 } 414 } 415 416 private void adjustOtherHeadsetPriorities( 417 HeadsetService hsService, List<BluetoothDevice> connectedDeviceList) { 418 for (BluetoothDevice device : mAdapterService.getBondedDevices()) { 419 if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT 420 && !connectedDeviceList.contains(device)) { 421 hsService.setPriority(device, BluetoothProfile.PRIORITY_ON); 422 } 423 } 424 } 425 426 private void adjustOtherSinkPriorities( 427 A2dpService a2dpService, BluetoothDevice connectedDevice) { 428 for (BluetoothDevice device : mAdapterService.getBondedDevices()) { 429 if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT 430 && !device.equals(connectedDevice)) { 431 a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON); 432 } 433 } 434 } 435 436 private static void debugLog(String msg) { 437 if (DBG) Log.d(TAG, msg); 438 } 439 440 private static void warnLog(String msg) { 441 Log.w(TAG, msg); 442 } 443 444 private static void errorLog(String msg) { 445 Log.e(TAG, msg); 446 } 447} 448