PhonePolicy.java revision 2cc754651ee7245e62fa247b1ca85acb6889e79e
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.HashSet;
45import 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.
72class 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