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