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.BluetoothUuid;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.os.Handler;
30import android.os.Looper;
31import android.os.Message;
32import android.os.ParcelUuid;
33import android.os.Parcelable;
34import android.support.annotation.VisibleForTesting;
35import android.util.Log;
36
37import com.android.bluetooth.a2dp.A2dpService;
38import com.android.bluetooth.hearingaid.HearingAidService;
39import com.android.bluetooth.hfp.HeadsetService;
40import com.android.bluetooth.hid.HidHostService;
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    private static final boolean DBG = true;
74    private static final String TAG = "BluetoothPhonePolicy";
75
76    // Message types for the handler (internal messages generated by intents or timeouts)
77    private static final int MESSAGE_PROFILE_CONNECTION_STATE_CHANGED = 1;
78    private static final int MESSAGE_PROFILE_INIT_PRIORITIES = 2;
79    private static final int MESSAGE_CONNECT_OTHER_PROFILES = 3;
80    private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;
81    private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
82
83    // Timeouts
84    @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
85
86    private final AdapterService mAdapterService;
87    private final ServiceFactory mFactory;
88    private final Handler mHandler;
89    private final HashSet<BluetoothDevice> mHeadsetRetrySet = new HashSet<>();
90    private final HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>();
91    private final HashSet<BluetoothDevice> mConnectOtherProfilesDeviceSet = new HashSet<>();
92
93    // Broadcast receiver for all changes to states of various profiles
94    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
95        @Override
96        public void onReceive(Context context, Intent intent) {
97            String action = intent.getAction();
98            if (action == null) {
99                errorLog("Received intent with null action");
100                return;
101            }
102            switch (action) {
103                case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
104                    mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
105                            BluetoothProfile.HEADSET, -1, // No-op argument
106                            intent).sendToTarget();
107                    break;
108                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
109                    mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
110                            BluetoothProfile.A2DP, -1, // No-op argument
111                            intent).sendToTarget();
112                    break;
113                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
114                    mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
115                            BluetoothProfile.A2DP, -1, // No-op argument
116                            intent).sendToTarget();
117                    break;
118                case BluetoothAdapter.ACTION_STATE_CHANGED:
119                    // Only pass the message on if the adapter has actually changed state from
120                    // non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON.
121                    int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
122                    if (newState == BluetoothAdapter.STATE_ON) {
123                        mHandler.obtainMessage(MESSAGE_ADAPTER_STATE_TURNED_ON).sendToTarget();
124                    }
125                    break;
126                case BluetoothDevice.ACTION_UUID:
127                    mHandler.obtainMessage(MESSAGE_PROFILE_INIT_PRIORITIES, intent).sendToTarget();
128                    break;
129                default:
130                    Log.e(TAG, "Received unexpected intent, action=" + action);
131                    break;
132            }
133        }
134    };
135
136    @VisibleForTesting
137    BroadcastReceiver getBroadcastReceiver() {
138        return mReceiver;
139    }
140
141    // Handler to handoff intents to class thread
142    class PhonePolicyHandler extends Handler {
143        PhonePolicyHandler(Looper looper) {
144            super(looper);
145        }
146
147        @Override
148        public void handleMessage(Message msg) {
149            switch (msg.what) {
150                case MESSAGE_PROFILE_INIT_PRIORITIES: {
151                    Intent intent = (Intent) msg.obj;
152                    BluetoothDevice device =
153                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
154                    Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
155                    debugLog("Received ACTION_UUID for device " + device);
156                    if (uuids != null) {
157                        ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length];
158                        for (int i = 0; i < uuidsToSend.length; i++) {
159                            uuidsToSend[i] = (ParcelUuid) uuids[i];
160                            debugLog("index=" + i + "uuid=" + uuidsToSend[i]);
161                        }
162                        processInitProfilePriorities(device, uuidsToSend);
163                    }
164                }
165                break;
166
167                case MESSAGE_PROFILE_CONNECTION_STATE_CHANGED: {
168                    Intent intent = (Intent) msg.obj;
169                    BluetoothDevice device =
170                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
171                    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
172                    int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
173                    processProfileStateChanged(device, msg.arg1, nextState, prevState);
174                }
175                break;
176
177                case MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED: {
178                    Intent intent = (Intent) msg.obj;
179                    BluetoothDevice activeDevice =
180                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
181                    processProfileActiveDeviceChanged(activeDevice, msg.arg1);
182                }
183                break;
184
185                case MESSAGE_CONNECT_OTHER_PROFILES: {
186                    // Called when we try connect some profiles in processConnectOtherProfiles but
187                    // we send a delayed message to try connecting the remaining profiles
188                    BluetoothDevice device = (BluetoothDevice) msg.obj;
189                    processConnectOtherProfiles(device);
190                    mConnectOtherProfilesDeviceSet.remove(device);
191                    break;
192                }
193                case MESSAGE_ADAPTER_STATE_TURNED_ON:
194                    // Call auto connect when adapter switches state to ON
195                    resetStates();
196                    autoConnect();
197                    break;
198            }
199        }
200    }
201
202    ;
203
204    // Policy API functions for lifecycle management (protected)
205    protected void start() {
206        IntentFilter filter = new IntentFilter();
207        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
208        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
209        filter.addAction(BluetoothDevice.ACTION_UUID);
210        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
211        filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
212        mAdapterService.registerReceiver(mReceiver, filter);
213    }
214
215    protected void cleanup() {
216        mAdapterService.unregisterReceiver(mReceiver);
217        resetStates();
218    }
219
220    PhonePolicy(AdapterService service, ServiceFactory factory) {
221        mAdapterService = service;
222        mFactory = factory;
223        mHandler = new PhonePolicyHandler(service.getMainLooper());
224    }
225
226    // Policy implementation, all functions MUST be private
227    private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) {
228        debugLog("processInitProfilePriorities() - device " + device);
229        HidHostService hidService = mFactory.getHidHostService();
230        A2dpService a2dpService = mFactory.getA2dpService();
231        HeadsetService headsetService = mFactory.getHeadsetService();
232        PanService panService = mFactory.getPanService();
233        HearingAidService hearingAidService = mFactory.getHearingAidService();
234
235        // Set profile priorities only for the profiles discovered on the remote device.
236        // This avoids needless auto-connect attempts to profiles non-existent on the remote device
237        if ((hidService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid)
238                || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) && (
239                hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
240            hidService.setPriority(device, BluetoothProfile.PRIORITY_ON);
241        }
242
243        // If we do not have a stored priority for HFP/A2DP (all roles) then default to on.
244        if ((headsetService != null) && ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)
245                || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) && (
246                headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
247            headsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
248        }
249
250        if ((a2dpService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)
251                || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) && (
252                a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
253            a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
254        }
255
256        if ((panService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU) && (
257                panService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)
258                && mAdapterService.getResources()
259                .getBoolean(R.bool.config_bluetooth_pan_enable_autoconnect))) {
260            panService.setPriority(device, BluetoothProfile.PRIORITY_ON);
261        }
262
263        if ((hearingAidService != null) && BluetoothUuid.isUuidPresent(uuids,
264                BluetoothUuid.HearingAid) && (hearingAidService.getPriority(device)
265                == BluetoothProfile.PRIORITY_UNDEFINED)) {
266            debugLog("setting hearing aid profile priority for device " + device);
267            hearingAidService.setPriority(device, BluetoothProfile.PRIORITY_ON);
268        }
269    }
270
271    private void processProfileStateChanged(BluetoothDevice device, int profileId, int nextState,
272            int prevState) {
273        debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", "
274                + prevState + " -> " + nextState);
275        if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET))) {
276            if (nextState == BluetoothProfile.STATE_CONNECTED) {
277                switch (profileId) {
278                    case BluetoothProfile.A2DP:
279                        mA2dpRetrySet.remove(device);
280                        break;
281                    case BluetoothProfile.HEADSET:
282                        mHeadsetRetrySet.remove(device);
283                        break;
284                }
285                connectOtherProfile(device);
286            }
287            if (prevState == BluetoothProfile.STATE_CONNECTING
288                    && nextState == BluetoothProfile.STATE_DISCONNECTED) {
289                HeadsetService hsService = mFactory.getHeadsetService();
290                boolean hsDisconnected = hsService == null || hsService.getConnectionState(device)
291                        == BluetoothProfile.STATE_DISCONNECTED;
292                A2dpService a2dpService = mFactory.getA2dpService();
293                boolean a2dpDisconnected = a2dpService == null
294                        || a2dpService.getConnectionState(device)
295                        == BluetoothProfile.STATE_DISCONNECTED;
296                debugLog("processProfileStateChanged, device=" + device + ", a2dpDisconnected="
297                        + a2dpDisconnected + ", hsDisconnected=" + hsDisconnected);
298                if (hsDisconnected && a2dpDisconnected) {
299                    removeAutoConnectFromA2dpSink(device);
300                    removeAutoConnectFromHeadset(device);
301                }
302            }
303        }
304    }
305
306    private void processProfileActiveDeviceChanged(BluetoothDevice activeDevice, int profileId) {
307        debugLog("processProfileActiveDeviceChanged, activeDevice=" + activeDevice + ", profile="
308                + profileId);
309        switch (profileId) {
310            // Tracking active device changed intent only for A2DP so that we always connect to a
311            // single device after toggling Bluetooth
312            case BluetoothProfile.A2DP:
313                // Ignore null active device since we don't know if the change is triggered by
314                // normal device disconnection during Bluetooth shutdown or user action
315                if (activeDevice == null) {
316                    warnLog("processProfileActiveDeviceChanged: ignore null A2DP active device");
317                    return;
318                }
319                for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
320                    removeAutoConnectFromA2dpSink(device);
321                    removeAutoConnectFromHeadset(device);
322                }
323                setAutoConnectForA2dpSink(activeDevice);
324                setAutoConnectForHeadset(activeDevice);
325                break;
326        }
327    }
328
329    private void resetStates() {
330        mHeadsetRetrySet.clear();
331        mA2dpRetrySet.clear();
332    }
333
334    private void autoConnect() {
335        if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
336            errorLog("autoConnect: BT is not ON. Exiting autoConnect");
337            return;
338        }
339
340        if (!mAdapterService.isQuietModeEnabled()) {
341            debugLog("autoConnect: Initiate auto connection on BT on...");
342            final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
343            if (bondedDevices == null) {
344                errorLog("autoConnect: bondedDevices are null");
345                return;
346            }
347            for (BluetoothDevice device : bondedDevices) {
348                autoConnectHeadset(device);
349                autoConnectA2dp(device);
350            }
351        } else {
352            debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections");
353        }
354    }
355
356    private void autoConnectA2dp(BluetoothDevice device) {
357        final A2dpService a2dpService = mFactory.getA2dpService();
358        if (a2dpService == null) {
359            warnLog("autoConnectA2dp: service is null, failed to connect to " + device);
360            return;
361        }
362        int a2dpPriority = a2dpService.getPriority(device);
363        if (a2dpPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
364            debugLog("autoConnectA2dp: connecting A2DP with " + device);
365            a2dpService.connect(device);
366        } else {
367            debugLog("autoConnectA2dp: skipped auto-connect A2DP with device " + device
368                    + " priority " + a2dpPriority);
369        }
370    }
371
372    private void autoConnectHeadset(BluetoothDevice device) {
373        final HeadsetService hsService = mFactory.getHeadsetService();
374        if (hsService == null) {
375            warnLog("autoConnectHeadset: service is null, failed to connect to " + device);
376            return;
377        }
378        int headsetPriority = hsService.getPriority(device);
379        if (headsetPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
380            debugLog("autoConnectHeadset: Connecting HFP with " + device);
381            hsService.connect(device);
382        } else {
383            debugLog("autoConnectHeadset: skipped auto-connect HFP with device " + device
384                    + " priority " + headsetPriority);
385        }
386    }
387
388    private void connectOtherProfile(BluetoothDevice device) {
389        if (mAdapterService.isQuietModeEnabled()) {
390            debugLog("connectOtherProfile: in quiet mode, skip connect other profile " + device);
391            return;
392        }
393        if (mConnectOtherProfilesDeviceSet.contains(device)) {
394            debugLog("connectOtherProfile: already scheduled callback for " + device);
395            return;
396        }
397        mConnectOtherProfilesDeviceSet.add(device);
398        Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES);
399        m.obj = device;
400        mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis);
401    }
402
403    // This function is called whenever a profile is connected.  This allows any other bluetooth
404    // profiles which are not already connected or in the process of connecting to attempt to
405    // connect to the device that initiated the connection.  In the event that this function is
406    // invoked and there are no current bluetooth connections no new profiles will be connected.
407    private void processConnectOtherProfiles(BluetoothDevice device) {
408        debugLog("processConnectOtherProfiles, device=" + device);
409        if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
410            warnLog("processConnectOtherProfiles, adapter is not ON " + mAdapterService.getState());
411            return;
412        }
413        HeadsetService hsService = mFactory.getHeadsetService();
414        A2dpService a2dpService = mFactory.getA2dpService();
415        PanService panService = mFactory.getPanService();
416
417        boolean atLeastOneProfileConnectedForDevice = false;
418        boolean allProfilesEmpty = true;
419        List<BluetoothDevice> a2dpConnDevList = null;
420        List<BluetoothDevice> hsConnDevList = null;
421        List<BluetoothDevice> panConnDevList = null;
422
423        if (hsService != null) {
424            hsConnDevList = hsService.getConnectedDevices();
425            allProfilesEmpty &= hsConnDevList.isEmpty();
426            atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
427        }
428        if (a2dpService != null) {
429            a2dpConnDevList = a2dpService.getConnectedDevices();
430            allProfilesEmpty &= a2dpConnDevList.isEmpty();
431            atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
432        }
433        if (panService != null) {
434            panConnDevList = panService.getConnectedDevices();
435            allProfilesEmpty &= panConnDevList.isEmpty();
436            atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
437        }
438
439        if (!atLeastOneProfileConnectedForDevice) {
440            // Consider this device as fully disconnected, don't bother connecting others
441            debugLog("processConnectOtherProfiles, all profiles disconnected for " + device);
442            mHeadsetRetrySet.remove(device);
443            mA2dpRetrySet.remove(device);
444            if (allProfilesEmpty) {
445                debugLog("processConnectOtherProfiles, all profiles disconnected for all devices");
446                // reset retry status so that in the next round we can start retrying connections
447                resetStates();
448            }
449            return;
450        }
451
452        if (hsService != null) {
453            if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
454                    >= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
455                    == BluetoothProfile.STATE_DISCONNECTED)) {
456                debugLog("Retrying connection to Headset with device " + device);
457                mHeadsetRetrySet.add(device);
458                hsService.connect(device);
459            }
460        }
461        if (a2dpService != null) {
462            if (!mA2dpRetrySet.contains(device) && (a2dpService.getPriority(device)
463                    >= BluetoothProfile.PRIORITY_ON) && (a2dpService.getConnectionState(device)
464                    == BluetoothProfile.STATE_DISCONNECTED)) {
465                debugLog("Retrying connection to A2DP with device " + device);
466                mA2dpRetrySet.add(device);
467                a2dpService.connect(device);
468            }
469        }
470        if (panService != null) {
471            // TODO: the panConnDevList.isEmpty() check below should be removed once
472            // Multi-PAN is supported.
473            if (panConnDevList.isEmpty() && (panService.getPriority(device)
474                    >= BluetoothProfile.PRIORITY_ON) && (panService.getConnectionState(device)
475                    == BluetoothProfile.STATE_DISCONNECTED)) {
476                debugLog("Retrying connection to PAN with device " + device);
477                panService.connect(device);
478            }
479        }
480    }
481
482    /**
483     * Set a device's headset profile priority to PRIORITY_AUTO_CONNECT if device support that
484     * profile
485     *
486     * @param device device whose headset profile priority should be PRIORITY_AUTO_CONNECT
487     */
488    private void setAutoConnectForHeadset(BluetoothDevice device) {
489        HeadsetService hsService = mFactory.getHeadsetService();
490        if (hsService == null) {
491            warnLog("setAutoConnectForHeadset: HEADSET service is null");
492            return;
493        }
494        if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
495            debugLog("setAutoConnectForHeadset: device " + device + " PRIORITY_AUTO_CONNECT");
496            hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
497        }
498    }
499
500    /**
501     * Set a device's A2DP profile priority to PRIORITY_AUTO_CONNECT if device support that profile
502     *
503     * @param device device whose headset profile priority should be PRIORITY_AUTO_CONNECT
504     */
505    private void setAutoConnectForA2dpSink(BluetoothDevice device) {
506        A2dpService a2dpService = mFactory.getA2dpService();
507        if (a2dpService == null) {
508            warnLog("setAutoConnectForA2dpSink: A2DP service is null");
509            return;
510        }
511        if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
512            debugLog("setAutoConnectForA2dpSink: device " + device + " PRIORITY_AUTO_CONNECT");
513            a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
514        }
515    }
516
517    /**
518     * Remove PRIORITY_AUTO_CONNECT from all headsets and set headset that used to have
519     * PRIORITY_AUTO_CONNECT to PRIORITY_ON
520     *
521     * @param device device whose PRIORITY_AUTO_CONNECT priority should be removed
522     */
523    private void removeAutoConnectFromHeadset(BluetoothDevice device) {
524        HeadsetService hsService = mFactory.getHeadsetService();
525        if (hsService == null) {
526            warnLog("removeAutoConnectFromHeadset: HEADSET service is null");
527            return;
528        }
529        if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT) {
530            debugLog("removeAutoConnectFromHeadset: device " + device + " PRIORITY_ON");
531            hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
532        }
533    }
534
535    /**
536     * Remove PRIORITY_AUTO_CONNECT from all A2DP sinks and set A2DP sink that used to have
537     * PRIORITY_AUTO_CONNECT to PRIORITY_ON
538     *
539     * @param device device whose PRIORITY_AUTO_CONNECT priority should be removed
540     */
541    private void removeAutoConnectFromA2dpSink(BluetoothDevice device) {
542        A2dpService a2dpService = mFactory.getA2dpService();
543        if (a2dpService == null) {
544            warnLog("removeAutoConnectFromA2dpSink: A2DP service is null");
545            return;
546        }
547        if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT) {
548            debugLog("removeAutoConnectFromA2dpSink: device " + device + " PRIORITY_ON");
549            a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
550        }
551    }
552
553    private static void debugLog(String msg) {
554        if (DBG) {
555            Log.i(TAG, msg);
556        }
557    }
558
559    private static void warnLog(String msg) {
560        Log.w(TAG, msg);
561    }
562
563    private static void errorLog(String msg) {
564        Log.e(TAG, msg);
565    }
566}
567