Home | History | Annotate | Download | only in btservice
      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