PhonePolicy.java revision b8f2c708191ab9b31eaff709d4767f3e4fa03164
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    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
121    public BroadcastReceiver getBroadcastReceiver() {
122        return mReceiver;
123    }
124
125    // Handler to handoff intents to class thread
126    class PhonePolicyHandler extends Handler {
127        PhonePolicyHandler(Looper looper) {
128            super(looper);
129        }
130
131        @Override
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)
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    }
183    protected void cleanup() {
184        mAdapterService.unregisterReceiver(mReceiver);
185    }
186
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
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
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
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
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
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
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.
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
378    private void debugLog(String msg) {
379        if (DBG) Log.d(TAG, msg);
380    }
381
382    private void errorLog(String msg) {
383        Log.e(TAG, msg);
384    }
385
386    void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId) {
387        switch (profileId) {
388            case BluetoothProfile.HEADSET:
389                HeadsetService hsService = mFactory.getHeadsetService();
390                if ((hsService != null)
391                        && (BluetoothProfile.PRIORITY_AUTO_CONNECT
392                                   != hsService.getPriority(device))) {
393                    List<BluetoothDevice> deviceList = hsService.getConnectedDevices();
394                    adjustOtherHeadsetPriorities(hsService, deviceList);
395                    hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
396                }
397                break;
398
399            case BluetoothProfile.A2DP:
400                A2dpService a2dpService = mFactory.getA2dpService();
401                if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT
402                                                     != a2dpService.getPriority(device))) {
403                    adjustOtherSinkPriorities(a2dpService, device);
404                    a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
405                }
406                break;
407
408            default:
409                Log.w(TAG, "Attempting to set Auto Connect priority on invalid profile");
410                break;
411        }
412    }
413
414    private void adjustOtherHeadsetPriorities(
415            HeadsetService hsService, List<BluetoothDevice> connectedDeviceList) {
416        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
417            if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
418                    && !connectedDeviceList.contains(device)) {
419                hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
420            }
421        }
422    }
423
424    private void adjustOtherSinkPriorities(
425            A2dpService a2dpService, BluetoothDevice connectedDevice) {
426        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
427            if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
428                    && !device.equals(connectedDevice)) {
429                a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
430            }
431        }
432    }
433}
434