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