BluetoothPbapService.java revision 776a51b650f0b0239758ee28a20af749b13e17a3
1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.android.bluetooth.pbap;
34
35import android.bluetooth.BluetoothAdapter;
36import android.bluetooth.BluetoothDevice;
37import android.bluetooth.BluetoothProfile;
38import android.bluetooth.BluetoothSocket;
39import android.bluetooth.IBluetoothPbap;
40import android.content.BroadcastReceiver;
41import android.content.Context;
42import android.content.Intent;
43import android.content.IntentFilter;
44import android.database.ContentObserver;
45import android.database.sqlite.SQLiteException;
46import android.os.Handler;
47import android.os.HandlerThread;
48import android.os.Looper;
49import android.os.Message;
50import android.os.PowerManager;
51import android.support.annotation.VisibleForTesting;
52import android.telephony.TelephonyManager;
53import android.text.TextUtils;
54import android.util.Log;
55
56import com.android.bluetooth.IObexConnectionHandler;
57import com.android.bluetooth.ObexServerSockets;
58import com.android.bluetooth.R;
59import com.android.bluetooth.Utils;
60import com.android.bluetooth.btservice.ProfileService;
61import com.android.bluetooth.sdp.SdpManager;
62import com.android.bluetooth.util.DevicePolicyUtils;
63
64import java.util.ArrayList;
65import java.util.HashMap;
66import java.util.List;
67
68public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler {
69    private static final String TAG = "BluetoothPbapService";
70
71    /**
72     * To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
73     * restart com.android.bluetooth process. only enable DEBUG log:
74     * "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and
75     * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE"
76     */
77
78    public static final boolean DEBUG = true;
79
80    public static final boolean VERBOSE = true;
81
82    /**
83     * Intent indicating incoming obex authentication request which is from
84     * PCE(Carkit)
85     */
86    static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall";
87
88    /**
89     * Intent indicating obex session key input complete by user which is sent
90     * from BluetoothPbapActivity
91     */
92    static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse";
93
94    /**
95     * Intent indicating user canceled obex authentication session key input
96     * which is sent from BluetoothPbapActivity
97     */
98    static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled";
99
100    /**
101     * Intent indicating timeout for user confirmation, which is sent to
102     * BluetoothPbapActivity
103     */
104    static final String USER_CONFIRM_TIMEOUT_ACTION =
105            "com.android.bluetooth.pbap.userconfirmtimeout";
106
107    /**
108     * Intent Extra name indicating session key which is sent from
109     * BluetoothPbapActivity
110     */
111    static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
112
113    static final String EXTRA_DEVICE = "com.android.bluetooth.pbap.device";
114
115    static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
116
117    static final int MSG_ACQUIRE_WAKE_LOCK = 5004;
118
119    static final int MSG_RELEASE_WAKE_LOCK = 5005;
120
121    static final int MSG_STATE_MACHINE_DONE = 5006;
122
123    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
124
125    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
126
127    private static final int START_LISTENER = 1;
128
129    static final int USER_TIMEOUT = 2;
130
131    private static final int SHUTDOWN = 4;
132
133    static final int LOAD_CONTACTS = 5;
134
135    private static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
136
137    static final int ROLLOVER_COUNTERS = 7;
138
139    static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
140
141    static final int RELEASE_WAKE_LOCK_DELAY = 10000;
142
143    private PowerManager.WakeLock mWakeLock;
144
145    private static String sLocalPhoneNum;
146
147    private static String sLocalPhoneName;
148
149    private ObexServerSockets mServerSockets = null;
150
151    private static final int SDP_PBAP_SERVER_VERSION = 0x0102;
152
153    private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0001;
154
155    private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F;
156
157    /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded).
158       The notification ID should be unique in Bluetooth package. */
159    private static final int PBAP_NOTIFICATION_ID_START = 1000000;
160    private static final int PBAP_NOTIFICATION_ID_END = 2000000;
161
162    private int mSdpHandle = -1;
163
164    protected Context mContext;
165
166    private PbapHandler mSessionStatusHandler;
167    private HandlerThread mHandlerThread;
168    private final HashMap<BluetoothDevice, PbapStateMachine> mPbapStateMachineMap = new HashMap<>();
169    private volatile int mNextNotificationId = PBAP_NOTIFICATION_ID_START;
170
171    // package and class name to which we send intent to check phone book access permission
172    private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
173    private static final String ACCESS_AUTHORITY_CLASS =
174            "com.android.settings.bluetooth.BluetoothPermissionRequest";
175
176    private Thread mThreadLoadContacts;
177
178    private Thread mThreadUpdateSecVersionCounter;
179
180    private class BluetoothPbapContentObserver extends ContentObserver {
181        BluetoothPbapContentObserver() {
182            super(new Handler());
183        }
184
185        @Override
186        public void onChange(boolean selfChange) {
187            Log.d(TAG, " onChange on contact uri ");
188            if (BluetoothPbapUtils.contactsLoaded) {
189                if (!mSessionStatusHandler.hasMessages(CHECK_SECONDARY_VERSION_COUNTER)) {
190                    mSessionStatusHandler.sendMessage(
191                            mSessionStatusHandler.obtainMessage(CHECK_SECONDARY_VERSION_COUNTER));
192                }
193            }
194        }
195    }
196
197    private BluetoothPbapContentObserver mContactChangeObserver;
198
199    private void parseIntent(final Intent intent) {
200        String action = intent.getAction();
201        if (DEBUG) {
202            Log.d(TAG, "action: " + action);
203        }
204        if (action == null) {
205            return;
206        }
207        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
208        if (DEBUG) {
209            Log.d(TAG, "state: " + state);
210        }
211
212        if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
213            int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
214                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
215            if (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
216                return;
217            }
218
219            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
220            synchronized (mPbapStateMachineMap) {
221                PbapStateMachine sm = mPbapStateMachineMap.get(device);
222                if (sm == null) {
223                    Log.w(TAG, "device not connected! device=" + device);
224                    return;
225                }
226                mSessionStatusHandler.removeMessages(USER_TIMEOUT, sm);
227                int access = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
228                        BluetoothDevice.CONNECTION_ACCESS_NO);
229                boolean savePreference = intent.getBooleanExtra(
230                        BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false);
231
232                if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
233                    if (savePreference) {
234                        device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
235                        if (VERBOSE) {
236                            Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)");
237                        }
238                    }
239                    sm.sendMessage(PbapStateMachine.AUTHORIZED);
240                } else {
241                    if (savePreference) {
242                        device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
243                        if (VERBOSE) {
244                            Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)");
245                        }
246                    }
247                    sm.sendMessage(PbapStateMachine.REJECTED);
248                }
249                return;
250            }
251        }
252
253        if (action.equals(AUTH_RESPONSE_ACTION)) {
254            String sessionKey = intent.getStringExtra(EXTRA_SESSION_KEY);
255            BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
256            synchronized (mPbapStateMachineMap) {
257                PbapStateMachine sm = mPbapStateMachineMap.get(device);
258                if (sm == null) {
259                    return;
260                }
261                Message msg = sm.obtainMessage(PbapStateMachine.AUTH_KEY_INPUT, sessionKey);
262                sm.sendMessage(msg);
263            }
264        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
265            BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
266            synchronized (mPbapStateMachineMap) {
267                PbapStateMachine sm = mPbapStateMachineMap.get(device);
268                if (sm == null) {
269                    return;
270                }
271                sm.sendMessage(PbapStateMachine.AUTH_CANCELLED);
272            }
273        } else {
274            Log.w(TAG, "Unrecognized intent!");
275        }
276    }
277
278    private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
279        @Override
280        public void onReceive(Context context, Intent intent) {
281            parseIntent(intent);
282        }
283    };
284
285    private void closeService() {
286        if (VERBOSE) {
287            Log.v(TAG, "Pbap Service closeService");
288        }
289
290        BluetoothPbapUtils.savePbapParams(this, BluetoothPbapUtils.sPrimaryVersionCounter,
291                BluetoothPbapUtils.sSecondaryVersionCounter, BluetoothPbapUtils.sDbIdentifier.get(),
292                BluetoothPbapUtils.contactsLastUpdated, BluetoothPbapUtils.totalFields,
293                BluetoothPbapUtils.totalSvcFields, BluetoothPbapUtils.totalContacts);
294
295        if (mWakeLock != null) {
296            mWakeLock.release();
297            mWakeLock = null;
298        }
299
300        cleanUpServerSocket();
301
302        if (mSessionStatusHandler != null) {
303            mSessionStatusHandler.removeCallbacksAndMessages(null);
304        }
305    }
306
307    private void cleanUpServerSocket() {
308        // Step 1, 2: clean up active server session and connection socket
309        synchronized (mPbapStateMachineMap) {
310            for (PbapStateMachine stateMachine : mPbapStateMachineMap.values()) {
311                stateMachine.sendMessage(PbapStateMachine.DISCONNECT);
312            }
313        }
314        // Step 3: clean up SDP record
315        cleanUpSdpRecord();
316        // Step 4: clean up existing server sockets
317        if (mServerSockets != null) {
318            mServerSockets.shutdown(false);
319            mServerSockets = null;
320        }
321    }
322
323    private void createSdpRecord() {
324        if (mSdpHandle > -1) {
325            Log.w(TAG, "createSdpRecord, SDP record already created");
326        }
327        mSdpHandle = SdpManager.getDefaultManager()
328                .createPbapPseRecord("OBEX Phonebook Access Server",
329                        mServerSockets.getRfcommChannel(), mServerSockets.getL2capPsm(),
330                        SDP_PBAP_SERVER_VERSION, SDP_PBAP_SUPPORTED_REPOSITORIES,
331                        SDP_PBAP_SUPPORTED_FEATURES);
332        if (DEBUG) {
333            Log.d(TAG, "created Sdp record, mSdpHandle=" + mSdpHandle);
334        }
335    }
336
337    private void cleanUpSdpRecord() {
338        if (mSdpHandle < 0) {
339            Log.w(TAG, "cleanUpSdpRecord, SDP record never created");
340            return;
341        }
342        int sdpHandle = mSdpHandle;
343        mSdpHandle = -1;
344        SdpManager sdpManager = SdpManager.getDefaultManager();
345        if (DEBUG) {
346            Log.d(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
347        }
348        if (sdpManager == null) {
349            Log.e(TAG, "sdpManager is null");
350        } else if (!sdpManager.removeSdpRecord(sdpHandle)) {
351            Log.w(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
352        }
353    }
354
355    private class PbapHandler extends Handler {
356        private PbapHandler(Looper looper) {
357            super(looper);
358        }
359
360        @Override
361        public void handleMessage(Message msg) {
362            if (VERBOSE) {
363                Log.v(TAG, "Handler(): got msg=" + msg.what);
364            }
365
366            switch (msg.what) {
367                case START_LISTENER:
368                    mServerSockets = ObexServerSockets.create(BluetoothPbapService.this);
369                    if (mServerSockets == null) {
370                        Log.w(TAG, "ObexServerSockets.create() returned null");
371                        break;
372                    }
373                    createSdpRecord();
374                    // fetch Pbap Params to check if significant change has happened to Database
375                    BluetoothPbapUtils.fetchPbapParams(mContext);
376                    break;
377                case USER_TIMEOUT:
378                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
379                    intent.setPackage(getString(R.string.pairing_ui_package));
380                    PbapStateMachine stateMachine = (PbapStateMachine) msg.obj;
381                    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, stateMachine.getRemoteDevice());
382                    intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
383                            BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
384                    sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
385                    stateMachine.sendMessage(PbapStateMachine.REJECTED);
386                    break;
387                case MSG_ACQUIRE_WAKE_LOCK:
388                    if (mWakeLock == null) {
389                        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
390                        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
391                                "StartingObexPbapTransaction");
392                        mWakeLock.setReferenceCounted(false);
393                        mWakeLock.acquire();
394                        Log.w(TAG, "Acquire Wake Lock");
395                    }
396                    mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
397                    mSessionStatusHandler.sendMessageDelayed(
398                            mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
399                            RELEASE_WAKE_LOCK_DELAY);
400                    break;
401                case MSG_RELEASE_WAKE_LOCK:
402                    if (mWakeLock != null) {
403                        mWakeLock.release();
404                        mWakeLock = null;
405                    }
406                    break;
407                case SHUTDOWN:
408                    closeService();
409                    break;
410                case LOAD_CONTACTS:
411                    loadAllContacts();
412                    break;
413                case CHECK_SECONDARY_VERSION_COUNTER:
414                    updateSecondaryVersion();
415                    break;
416                case ROLLOVER_COUNTERS:
417                    BluetoothPbapUtils.rolloverCounters();
418                    break;
419                case MSG_STATE_MACHINE_DONE:
420                    PbapStateMachine sm = (PbapStateMachine) msg.obj;
421                    BluetoothDevice remoteDevice = sm.getRemoteDevice();
422                    sm.quitNow();
423                    synchronized (mPbapStateMachineMap) {
424                        mPbapStateMachineMap.remove(remoteDevice);
425                    }
426                    break;
427                default:
428                    break;
429            }
430        }
431    }
432
433    int getConnectionState(BluetoothDevice device) {
434        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
435        if (mPbapStateMachineMap == null) {
436            return BluetoothProfile.STATE_DISCONNECTED;
437        }
438
439        synchronized (mPbapStateMachineMap) {
440            PbapStateMachine sm = mPbapStateMachineMap.get(device);
441            if (sm == null) {
442                return BluetoothProfile.STATE_DISCONNECTED;
443            }
444            return sm.getConnectionState();
445        }
446    }
447
448    List<BluetoothDevice> getConnectedDevices() {
449        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
450        if (mPbapStateMachineMap == null) {
451            return new ArrayList<>();
452        }
453        synchronized (mPbapStateMachineMap) {
454            return new ArrayList<>(mPbapStateMachineMap.keySet());
455        }
456    }
457
458    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
459        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
460        List<BluetoothDevice> devices = new ArrayList<>();
461        if (mPbapStateMachineMap == null || states == null) {
462            return devices;
463        }
464        synchronized (mPbapStateMachineMap) {
465            for (int state : states) {
466                for (BluetoothDevice device : mPbapStateMachineMap.keySet()) {
467                    if (state == mPbapStateMachineMap.get(device).getConnectionState()) {
468                        devices.add(device);
469                    }
470                }
471            }
472        }
473        return devices;
474    }
475
476    void disconnect(BluetoothDevice device) {
477        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
478        synchronized (mPbapStateMachineMap) {
479            PbapStateMachine sm = mPbapStateMachineMap.get(device);
480            if (sm != null) {
481                sm.sendMessage(PbapStateMachine.DISCONNECT);
482            }
483        }
484    }
485
486    static String getLocalPhoneNum() {
487        return sLocalPhoneNum;
488    }
489
490    static String getLocalPhoneName() {
491        return sLocalPhoneName;
492    }
493
494    @Override
495    protected IProfileServiceBinder initBinder() {
496        return new PbapBinder(this);
497    }
498
499    @Override
500    protected boolean start() {
501        if (VERBOSE) {
502            Log.v(TAG, "start()");
503        }
504        mContext = this;
505        mHandlerThread = new HandlerThread("PbapHandlerThread");
506        mHandlerThread.start();
507        mSessionStatusHandler = new PbapHandler(mHandlerThread.getLooper());
508        IntentFilter filter = new IntentFilter();
509        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
510        filter.addAction(AUTH_RESPONSE_ACTION);
511        filter.addAction(AUTH_CANCELLED_ACTION);
512        BluetoothPbapConfig.init(this);
513        registerReceiver(mPbapReceiver, filter);
514        try {
515            mContactChangeObserver = new BluetoothPbapContentObserver();
516            getContentResolver().registerContentObserver(
517                    DevicePolicyUtils.getEnterprisePhoneUri(this), false,
518                    mContactChangeObserver);
519        } catch (SQLiteException e) {
520            Log.e(TAG, "SQLite exception: " + e);
521        } catch (IllegalStateException e) {
522            Log.e(TAG, "Illegal state exception, content observer is already registered");
523        }
524
525        TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
526        if (tm != null) {
527            sLocalPhoneNum = tm.getLine1Number();
528            sLocalPhoneName = tm.getLine1AlphaTag();
529            if (TextUtils.isEmpty(sLocalPhoneName)) {
530                sLocalPhoneName = this.getString(R.string.localPhoneName);
531            }
532        }
533
534        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
535        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
536        return true;
537    }
538
539    @Override
540    protected boolean stop() {
541        if (VERBOSE) {
542            Log.v(TAG, "stop()");
543        }
544        if (mSessionStatusHandler != null) {
545            mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
546        }
547        if (mHandlerThread != null) {
548            mHandlerThread.quitSafely();
549        }
550        if (mContactChangeObserver == null) {
551            Log.i(TAG, "Avoid unregister when receiver it is not registered");
552            return true;
553        }
554        try {
555            unregisterReceiver(mPbapReceiver);
556            getContentResolver().unregisterContentObserver(mContactChangeObserver);
557            mContactChangeObserver = null;
558        } catch (Exception e) {
559            Log.w(TAG, "Unable to unregister pbap receiver", e);
560        }
561        return true;
562    }
563
564    private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder {
565        private BluetoothPbapService mService;
566
567        private BluetoothPbapService getService() {
568            if (!Utils.checkCaller()) {
569                Log.w(TAG, "not allowed for non-active user");
570                return null;
571            }
572            if (mService != null && mService.isAvailable()) {
573                return mService;
574            }
575            return null;
576        }
577
578        PbapBinder(BluetoothPbapService service) {
579            if (VERBOSE) {
580                Log.v(TAG, "PbapBinder()");
581            }
582            mService = service;
583        }
584
585        @Override
586        public void cleanup() {
587            mService = null;
588        }
589
590        @Override
591        public List<BluetoothDevice> getConnectedDevices() {
592            if (DEBUG) {
593                Log.d(TAG, "getConnectedDevices");
594            }
595            BluetoothPbapService service = getService();
596            if (service == null) {
597                return new ArrayList<>(0);
598            }
599            return service.getConnectedDevices();
600        }
601
602        @Override
603        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
604            if (DEBUG) {
605                Log.d(TAG, "getDevicesMatchingConnectionStates");
606            }
607            BluetoothPbapService service = getService();
608            if (service == null) {
609                return new ArrayList<>(0);
610            }
611            return service.getDevicesMatchingConnectionStates(states);
612        }
613
614        @Override
615        public int getConnectionState(BluetoothDevice device) {
616            if (DEBUG) {
617                Log.d(TAG, "getConnectionState: " + device);
618            }
619            BluetoothPbapService service = getService();
620            if (service == null) {
621                return BluetoothAdapter.STATE_DISCONNECTED;
622            }
623            return service.getConnectionState(device);
624        }
625
626        @Override
627        public void disconnect(BluetoothDevice device) {
628            if (DEBUG) {
629                Log.d(TAG, "disconnect");
630            }
631            BluetoothPbapService service = getService();
632            if (service == null) {
633                return;
634            }
635            service.disconnect(device);
636        }
637    }
638
639    @Override
640    public boolean onConnect(BluetoothDevice remoteDevice, BluetoothSocket socket) {
641        if (remoteDevice == null || socket == null) {
642            Log.e(TAG, "onConnect(): Unexpected null. remoteDevice=" + remoteDevice
643                    + " socket=" + socket);
644            return false;
645        }
646
647        PbapStateMachine sm = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice,
648                socket,  this, mSessionStatusHandler, mNextNotificationId);
649        mNextNotificationId++;
650        if (mNextNotificationId == PBAP_NOTIFICATION_ID_END) {
651            mNextNotificationId = PBAP_NOTIFICATION_ID_START;
652        }
653        synchronized (mPbapStateMachineMap) {
654            mPbapStateMachineMap.put(remoteDevice, sm);
655        }
656        sm.sendMessage(PbapStateMachine.REQUEST_PERMISSION);
657        return true;
658    }
659
660    /**
661     * Get the phonebook access permission for the device; if unknown, ask the user.
662     * Send the result to the state machine.
663     * @param stateMachine PbapStateMachine which sends the request
664     */
665    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
666    public void checkOrGetPhonebookPermission(PbapStateMachine stateMachine) {
667        BluetoothDevice device = stateMachine.getRemoteDevice();
668        int permission = device.getPhonebookAccessPermission();
669        if (DEBUG) {
670            Log.d(TAG, "getPhonebookAccessPermission() = " + permission);
671        }
672
673        if (permission == BluetoothDevice.ACCESS_ALLOWED) {
674            stateMachine.sendMessage(PbapStateMachine.AUTHORIZED);
675        } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
676            stateMachine.sendMessage(PbapStateMachine.REJECTED);
677        } else { // permission == BluetoothDevice.ACCESS_UNKNOWN
678            Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
679            intent.setClassName(BluetoothPbapService.ACCESS_AUTHORITY_PACKAGE,
680                    BluetoothPbapService.ACCESS_AUTHORITY_CLASS);
681            intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
682                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
683            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
684            intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, this.getPackageName());
685            this.sendOrderedBroadcast(intent, BluetoothPbapService.BLUETOOTH_ADMIN_PERM);
686            if (VERBOSE) {
687                Log.v(TAG, "waiting for authorization for connection from: " + device);
688            }
689            /* In case car kit time out and try to use HFP for phonebook
690             * access, while UI still there waiting for user to confirm */
691            Message msg = mSessionStatusHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT,
692                    stateMachine);
693            mSessionStatusHandler.sendMessageDelayed(msg, USER_CONFIRM_TIMEOUT_VALUE);
694            /* We will continue the process when we receive
695             * BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app. */
696        }
697    }
698
699    /**
700     * Called when an unrecoverable error occurred in an accept thread.
701     * Close down the server socket, and restart.
702     */
703    @Override
704    public synchronized void onAcceptFailed() {
705        Log.w(TAG, "PBAP server socket accept thread failed. Restarting the server socket");
706
707        if (mWakeLock != null) {
708            mWakeLock.release();
709            mWakeLock = null;
710        }
711
712        cleanUpServerSocket();
713
714        if (mSessionStatusHandler != null) {
715            mSessionStatusHandler.removeCallbacksAndMessages(null);
716        }
717
718        synchronized (mPbapStateMachineMap) {
719            mPbapStateMachineMap.clear();
720        }
721
722        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
723    }
724
725    private void loadAllContacts() {
726        if (mThreadLoadContacts == null) {
727            Runnable r = new Runnable() {
728                @Override
729                public void run() {
730                    BluetoothPbapUtils.loadAllContacts(mContext,
731                            mSessionStatusHandler);
732                    mThreadLoadContacts = null;
733                }
734            };
735            mThreadLoadContacts = new Thread(r);
736            mThreadLoadContacts.start();
737        }
738    }
739
740    private void updateSecondaryVersion() {
741        if (mThreadUpdateSecVersionCounter == null) {
742            Runnable r = new Runnable() {
743                @Override
744                public void run() {
745                    BluetoothPbapUtils.updateSecondaryVersionCounter(mContext,
746                            mSessionStatusHandler);
747                    mThreadUpdateSecVersionCounter = null;
748                }
749            };
750            mThreadUpdateSecVersionCounter = new Thread(r);
751            mThreadUpdateSecVersionCounter.start();
752        }
753    }
754}
755