BluetoothPbapService.java revision bceb51ca01959f3871648a6f68aa9f2779f1910b
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    static final String EXTRA_DEVICE = "com.android.bluetooth.pbap.device";
113    static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
114
115    static final int MSG_ACQUIRE_WAKE_LOCK = 5004;
116    static final int MSG_RELEASE_WAKE_LOCK = 5005;
117    static final int MSG_STATE_MACHINE_DONE = 5006;
118
119    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
120    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
121
122    static final int START_LISTENER = 1;
123    static final int USER_TIMEOUT = 2;
124    static final int SHUTDOWN = 3;
125    static final int LOAD_CONTACTS = 4;
126    static final int CONTACTS_LOADED = 5;
127    static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
128    static final int ROLLOVER_COUNTERS = 7;
129
130    static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
131    static final int RELEASE_WAKE_LOCK_DELAY = 10000;
132
133    private PowerManager.WakeLock mWakeLock;
134
135    private static String sLocalPhoneNum;
136    private static String sLocalPhoneName;
137
138    private ObexServerSockets mServerSockets = null;
139
140    private static final int SDP_PBAP_SERVER_VERSION = 0x0102;
141    private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0001;
142    private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F;
143
144    /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded).
145       The notification ID should be unique in Bluetooth package. */
146    private static final int PBAP_NOTIFICATION_ID_START = 1000000;
147    private static final int PBAP_NOTIFICATION_ID_END = 2000000;
148
149    private int mSdpHandle = -1;
150
151    protected Context mContext;
152
153    private PbapHandler mSessionStatusHandler;
154    private HandlerThread mHandlerThread;
155    private final HashMap<BluetoothDevice, PbapStateMachine> mPbapStateMachineMap = new HashMap<>();
156    private volatile int mNextNotificationId = PBAP_NOTIFICATION_ID_START;
157
158    // package and class name to which we send intent to check phone book access permission
159    private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
160    private static final String ACCESS_AUTHORITY_CLASS =
161            "com.android.settings.bluetooth.BluetoothPermissionRequest";
162
163    private Thread mThreadLoadContacts;
164    private boolean mContactsLoaded = false;
165
166    private Thread mThreadUpdateSecVersionCounter;
167
168    private static BluetoothPbapService sBluetoothPbapService;
169
170    private class BluetoothPbapContentObserver extends ContentObserver {
171        BluetoothPbapContentObserver() {
172            super(new Handler());
173        }
174
175        @Override
176        public void onChange(boolean selfChange) {
177            Log.d(TAG, " onChange on contact uri ");
178            if (mContactsLoaded) {
179                if (!mSessionStatusHandler.hasMessages(CHECK_SECONDARY_VERSION_COUNTER)) {
180                    mSessionStatusHandler.sendMessage(
181                            mSessionStatusHandler.obtainMessage(CHECK_SECONDARY_VERSION_COUNTER));
182                }
183            }
184        }
185    }
186
187    private BluetoothPbapContentObserver mContactChangeObserver;
188
189    private void parseIntent(final Intent intent) {
190        String action = intent.getAction();
191        if (DEBUG) {
192            Log.d(TAG, "action: " + action);
193        }
194        if (BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY.equals(action)) {
195            int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
196                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
197            if (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
198                return;
199            }
200
201            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
202            synchronized (mPbapStateMachineMap) {
203                PbapStateMachine sm = mPbapStateMachineMap.get(device);
204                if (sm == null) {
205                    Log.w(TAG, "device not connected! device=" + device);
206                    return;
207                }
208                mSessionStatusHandler.removeMessages(USER_TIMEOUT, sm);
209                int access = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
210                        BluetoothDevice.CONNECTION_ACCESS_NO);
211                boolean savePreference = intent.getBooleanExtra(
212                        BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false);
213
214                if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
215                    if (savePreference) {
216                        device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
217                        if (VERBOSE) {
218                            Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)");
219                        }
220                    }
221                    sm.sendMessage(PbapStateMachine.AUTHORIZED);
222                } else {
223                    if (savePreference) {
224                        device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
225                        if (VERBOSE) {
226                            Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)");
227                        }
228                    }
229                    sm.sendMessage(PbapStateMachine.REJECTED);
230                }
231            }
232        } else if (AUTH_RESPONSE_ACTION.equals(action)) {
233            String sessionKey = intent.getStringExtra(EXTRA_SESSION_KEY);
234            BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
235            synchronized (mPbapStateMachineMap) {
236                PbapStateMachine sm = mPbapStateMachineMap.get(device);
237                if (sm == null) {
238                    return;
239                }
240                Message msg = sm.obtainMessage(PbapStateMachine.AUTH_KEY_INPUT, sessionKey);
241                sm.sendMessage(msg);
242            }
243        } else if (AUTH_CANCELLED_ACTION.equals(action)) {
244            BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
245            synchronized (mPbapStateMachineMap) {
246                PbapStateMachine sm = mPbapStateMachineMap.get(device);
247                if (sm == null) {
248                    return;
249                }
250                sm.sendMessage(PbapStateMachine.AUTH_CANCELLED);
251            }
252        } else {
253            Log.w(TAG, "Unhandled intent action: " + action);
254        }
255    }
256
257    private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
258        @Override
259        public void onReceive(Context context, Intent intent) {
260            parseIntent(intent);
261        }
262    };
263
264    private void closeService() {
265        if (VERBOSE) {
266            Log.v(TAG, "Pbap Service closeService");
267        }
268
269        BluetoothPbapUtils.savePbapParams(this);
270
271        if (mWakeLock != null) {
272            mWakeLock.release();
273            mWakeLock = null;
274        }
275
276        cleanUpServerSocket();
277
278        if (mSessionStatusHandler != null) {
279            mSessionStatusHandler.removeCallbacksAndMessages(null);
280        }
281    }
282
283    private void cleanUpServerSocket() {
284        // Step 1, 2: clean up active server session and connection socket
285        synchronized (mPbapStateMachineMap) {
286            for (PbapStateMachine stateMachine : mPbapStateMachineMap.values()) {
287                stateMachine.sendMessage(PbapStateMachine.DISCONNECT);
288            }
289        }
290        // Step 3: clean up SDP record
291        cleanUpSdpRecord();
292        // Step 4: clean up existing server sockets
293        if (mServerSockets != null) {
294            mServerSockets.shutdown(false);
295            mServerSockets = null;
296        }
297    }
298
299    private void createSdpRecord() {
300        if (mSdpHandle > -1) {
301            Log.w(TAG, "createSdpRecord, SDP record already created");
302        }
303        mSdpHandle = SdpManager.getDefaultManager()
304                .createPbapPseRecord("OBEX Phonebook Access Server",
305                        mServerSockets.getRfcommChannel(), mServerSockets.getL2capPsm(),
306                        SDP_PBAP_SERVER_VERSION, SDP_PBAP_SUPPORTED_REPOSITORIES,
307                        SDP_PBAP_SUPPORTED_FEATURES);
308        if (DEBUG) {
309            Log.d(TAG, "created Sdp record, mSdpHandle=" + mSdpHandle);
310        }
311    }
312
313    private void cleanUpSdpRecord() {
314        if (mSdpHandle < 0) {
315            Log.w(TAG, "cleanUpSdpRecord, SDP record never created");
316            return;
317        }
318        int sdpHandle = mSdpHandle;
319        mSdpHandle = -1;
320        SdpManager sdpManager = SdpManager.getDefaultManager();
321        if (DEBUG) {
322            Log.d(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
323        }
324        if (sdpManager == null) {
325            Log.e(TAG, "sdpManager is null");
326        } else if (!sdpManager.removeSdpRecord(sdpHandle)) {
327            Log.w(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
328        }
329    }
330
331    private class PbapHandler extends Handler {
332        private PbapHandler(Looper looper) {
333            super(looper);
334        }
335
336        @Override
337        public void handleMessage(Message msg) {
338            if (VERBOSE) {
339                Log.v(TAG, "Handler(): got msg=" + msg.what);
340            }
341
342            switch (msg.what) {
343                case START_LISTENER:
344                    mServerSockets = ObexServerSockets.create(BluetoothPbapService.this);
345                    if (mServerSockets == null) {
346                        Log.w(TAG, "ObexServerSockets.create() returned null");
347                        break;
348                    }
349                    createSdpRecord();
350                    // fetch Pbap Params to check if significant change has happened to Database
351                    BluetoothPbapUtils.fetchPbapParams(mContext);
352                    break;
353                case USER_TIMEOUT:
354                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
355                    intent.setPackage(getString(R.string.pairing_ui_package));
356                    PbapStateMachine stateMachine = (PbapStateMachine) msg.obj;
357                    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, stateMachine.getRemoteDevice());
358                    intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
359                            BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
360                    sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
361                    stateMachine.sendMessage(PbapStateMachine.REJECTED);
362                    break;
363                case MSG_ACQUIRE_WAKE_LOCK:
364                    if (mWakeLock == null) {
365                        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
366                        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
367                                "StartingObexPbapTransaction");
368                        mWakeLock.setReferenceCounted(false);
369                        mWakeLock.acquire();
370                        Log.w(TAG, "Acquire Wake Lock");
371                    }
372                    mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
373                    mSessionStatusHandler.sendMessageDelayed(
374                            mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
375                            RELEASE_WAKE_LOCK_DELAY);
376                    break;
377                case MSG_RELEASE_WAKE_LOCK:
378                    if (mWakeLock != null) {
379                        mWakeLock.release();
380                        mWakeLock = null;
381                    }
382                    break;
383                case SHUTDOWN:
384                    closeService();
385                    break;
386                case LOAD_CONTACTS:
387                    loadAllContacts();
388                    break;
389                case CONTACTS_LOADED:
390                    mContactsLoaded = true;
391                    break;
392                case CHECK_SECONDARY_VERSION_COUNTER:
393                    updateSecondaryVersion();
394                    break;
395                case ROLLOVER_COUNTERS:
396                    BluetoothPbapUtils.rolloverCounters();
397                    break;
398                case MSG_STATE_MACHINE_DONE:
399                    PbapStateMachine sm = (PbapStateMachine) msg.obj;
400                    BluetoothDevice remoteDevice = sm.getRemoteDevice();
401                    sm.quitNow();
402                    synchronized (mPbapStateMachineMap) {
403                        mPbapStateMachineMap.remove(remoteDevice);
404                    }
405                    break;
406                default:
407                    break;
408            }
409        }
410    }
411
412    int getConnectionState(BluetoothDevice device) {
413        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
414        if (mPbapStateMachineMap == null) {
415            return BluetoothProfile.STATE_DISCONNECTED;
416        }
417
418        synchronized (mPbapStateMachineMap) {
419            PbapStateMachine sm = mPbapStateMachineMap.get(device);
420            if (sm == null) {
421                return BluetoothProfile.STATE_DISCONNECTED;
422            }
423            return sm.getConnectionState();
424        }
425    }
426
427    List<BluetoothDevice> getConnectedDevices() {
428        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
429        if (mPbapStateMachineMap == null) {
430            return new ArrayList<>();
431        }
432        synchronized (mPbapStateMachineMap) {
433            return new ArrayList<>(mPbapStateMachineMap.keySet());
434        }
435    }
436
437    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
438        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
439        List<BluetoothDevice> devices = new ArrayList<>();
440        if (mPbapStateMachineMap == null || states == null) {
441            return devices;
442        }
443        synchronized (mPbapStateMachineMap) {
444            for (int state : states) {
445                for (BluetoothDevice device : mPbapStateMachineMap.keySet()) {
446                    if (state == mPbapStateMachineMap.get(device).getConnectionState()) {
447                        devices.add(device);
448                    }
449                }
450            }
451        }
452        return devices;
453    }
454
455    void disconnect(BluetoothDevice device) {
456        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
457        synchronized (mPbapStateMachineMap) {
458            PbapStateMachine sm = mPbapStateMachineMap.get(device);
459            if (sm != null) {
460                sm.sendMessage(PbapStateMachine.DISCONNECT);
461            }
462        }
463    }
464
465    static String getLocalPhoneNum() {
466        return sLocalPhoneNum;
467    }
468
469    static String getLocalPhoneName() {
470        return sLocalPhoneName;
471    }
472
473    @Override
474    protected IProfileServiceBinder initBinder() {
475        return new PbapBinder(this);
476    }
477
478    @Override
479    protected boolean start() {
480        if (VERBOSE) {
481            Log.v(TAG, "start()");
482        }
483        mContext = this;
484        mContactsLoaded = false;
485        mHandlerThread = new HandlerThread("PbapHandlerThread");
486        mHandlerThread.start();
487        mSessionStatusHandler = new PbapHandler(mHandlerThread.getLooper());
488        IntentFilter filter = new IntentFilter();
489        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
490        filter.addAction(AUTH_RESPONSE_ACTION);
491        filter.addAction(AUTH_CANCELLED_ACTION);
492        BluetoothPbapConfig.init(this);
493        registerReceiver(mPbapReceiver, filter);
494        try {
495            mContactChangeObserver = new BluetoothPbapContentObserver();
496            getContentResolver().registerContentObserver(
497                    DevicePolicyUtils.getEnterprisePhoneUri(this), false,
498                    mContactChangeObserver);
499        } catch (SQLiteException e) {
500            Log.e(TAG, "SQLite exception: " + e);
501        } catch (IllegalStateException e) {
502            Log.e(TAG, "Illegal state exception, content observer is already registered");
503        }
504
505        TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
506        if (tm != null) {
507            sLocalPhoneNum = tm.getLine1Number();
508            sLocalPhoneName = tm.getLine1AlphaTag();
509            if (TextUtils.isEmpty(sLocalPhoneName)) {
510                sLocalPhoneName = this.getString(R.string.localPhoneName);
511            }
512        }
513
514        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
515        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
516        setBluetoothPbapService(this);
517        return true;
518    }
519
520    @Override
521    protected boolean stop() {
522        if (VERBOSE) {
523            Log.v(TAG, "stop()");
524        }
525        setBluetoothPbapService(null);
526        if (mSessionStatusHandler != null) {
527            mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
528        }
529        if (mHandlerThread != null) {
530            mHandlerThread.quitSafely();
531        }
532        mContactsLoaded = false;
533        if (mContactChangeObserver == null) {
534            Log.i(TAG, "Avoid unregister when receiver it is not registered");
535            return true;
536        }
537        unregisterReceiver(mPbapReceiver);
538        getContentResolver().unregisterContentObserver(mContactChangeObserver);
539        mContactChangeObserver = null;
540        return true;
541    }
542
543    /**
544     * Get the current instance of {@link BluetoothPbapService}
545     *
546     * @return current instance of {@link BluetoothPbapService}
547     */
548    @VisibleForTesting
549    public static synchronized BluetoothPbapService getBluetoothPbapService() {
550        if (sBluetoothPbapService == null) {
551            Log.w(TAG, "getBluetoothPbapService(): service is null");
552            return null;
553        }
554        if (!sBluetoothPbapService.isAvailable()) {
555            Log.w(TAG, "getBluetoothPbapService(): service is not available");
556            return null;
557        }
558        return sBluetoothPbapService;
559    }
560
561    private static synchronized void setBluetoothPbapService(BluetoothPbapService instance) {
562        if (DEBUG) {
563            Log.d(TAG, "setBluetoothPbapService(): set to: " + instance);
564        }
565        sBluetoothPbapService = instance;
566    }
567
568    private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder {
569        private BluetoothPbapService mService;
570
571        private BluetoothPbapService getService() {
572            if (!Utils.checkCaller()) {
573                Log.w(TAG, "not allowed for non-active user");
574                return null;
575            }
576            if (mService != null && mService.isAvailable()) {
577                return mService;
578            }
579            return null;
580        }
581
582        PbapBinder(BluetoothPbapService service) {
583            if (VERBOSE) {
584                Log.v(TAG, "PbapBinder()");
585            }
586            mService = service;
587        }
588
589        @Override
590        public void cleanup() {
591            mService = null;
592        }
593
594        @Override
595        public List<BluetoothDevice> getConnectedDevices() {
596            if (DEBUG) {
597                Log.d(TAG, "getConnectedDevices");
598            }
599            BluetoothPbapService service = getService();
600            if (service == null) {
601                return new ArrayList<>(0);
602            }
603            return service.getConnectedDevices();
604        }
605
606        @Override
607        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
608            if (DEBUG) {
609                Log.d(TAG, "getDevicesMatchingConnectionStates");
610            }
611            BluetoothPbapService service = getService();
612            if (service == null) {
613                return new ArrayList<>(0);
614            }
615            return service.getDevicesMatchingConnectionStates(states);
616        }
617
618        @Override
619        public int getConnectionState(BluetoothDevice device) {
620            if (DEBUG) {
621                Log.d(TAG, "getConnectionState: " + device);
622            }
623            BluetoothPbapService service = getService();
624            if (service == null) {
625                return BluetoothAdapter.STATE_DISCONNECTED;
626            }
627            return service.getConnectionState(device);
628        }
629
630        @Override
631        public void disconnect(BluetoothDevice device) {
632            if (DEBUG) {
633                Log.d(TAG, "disconnect");
634            }
635            BluetoothPbapService service = getService();
636            if (service == null) {
637                return;
638            }
639            service.disconnect(device);
640        }
641    }
642
643    @Override
644    public boolean onConnect(BluetoothDevice remoteDevice, BluetoothSocket socket) {
645        if (remoteDevice == null || socket == null) {
646            Log.e(TAG, "onConnect(): Unexpected null. remoteDevice=" + remoteDevice
647                    + " socket=" + socket);
648            return false;
649        }
650
651        PbapStateMachine sm = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice,
652                socket,  this, mSessionStatusHandler, mNextNotificationId);
653        mNextNotificationId++;
654        if (mNextNotificationId == PBAP_NOTIFICATION_ID_END) {
655            mNextNotificationId = PBAP_NOTIFICATION_ID_START;
656        }
657        synchronized (mPbapStateMachineMap) {
658            mPbapStateMachineMap.put(remoteDevice, sm);
659        }
660        sm.sendMessage(PbapStateMachine.REQUEST_PERMISSION);
661        return true;
662    }
663
664    /**
665     * Get the phonebook access permission for the device; if unknown, ask the user.
666     * Send the result to the state machine.
667     * @param stateMachine PbapStateMachine which sends the request
668     */
669    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
670    public void checkOrGetPhonebookPermission(PbapStateMachine stateMachine) {
671        BluetoothDevice device = stateMachine.getRemoteDevice();
672        int permission = device.getPhonebookAccessPermission();
673        if (DEBUG) {
674            Log.d(TAG, "getPhonebookAccessPermission() = " + permission);
675        }
676
677        if (permission == BluetoothDevice.ACCESS_ALLOWED) {
678            stateMachine.sendMessage(PbapStateMachine.AUTHORIZED);
679        } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
680            stateMachine.sendMessage(PbapStateMachine.REJECTED);
681        } else { // permission == BluetoothDevice.ACCESS_UNKNOWN
682            Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
683            intent.setClassName(BluetoothPbapService.ACCESS_AUTHORITY_PACKAGE,
684                    BluetoothPbapService.ACCESS_AUTHORITY_CLASS);
685            intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
686                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
687            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
688            intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, this.getPackageName());
689            this.sendOrderedBroadcast(intent, BluetoothPbapService.BLUETOOTH_ADMIN_PERM);
690            if (VERBOSE) {
691                Log.v(TAG, "waiting for authorization for connection from: " + device);
692            }
693            /* In case car kit time out and try to use HFP for phonebook
694             * access, while UI still there waiting for user to confirm */
695            Message msg = mSessionStatusHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT,
696                    stateMachine);
697            mSessionStatusHandler.sendMessageDelayed(msg, USER_CONFIRM_TIMEOUT_VALUE);
698            /* We will continue the process when we receive
699             * BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app. */
700        }
701    }
702
703    /**
704     * Called when an unrecoverable error occurred in an accept thread.
705     * Close down the server socket, and restart.
706     */
707    @Override
708    public synchronized void onAcceptFailed() {
709        Log.w(TAG, "PBAP server socket accept thread failed. Restarting the server socket");
710
711        if (mWakeLock != null) {
712            mWakeLock.release();
713            mWakeLock = null;
714        }
715
716        cleanUpServerSocket();
717
718        if (mSessionStatusHandler != null) {
719            mSessionStatusHandler.removeCallbacksAndMessages(null);
720        }
721
722        synchronized (mPbapStateMachineMap) {
723            mPbapStateMachineMap.clear();
724        }
725
726        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
727    }
728
729    private void loadAllContacts() {
730        if (mThreadLoadContacts == null) {
731            Runnable r = new Runnable() {
732                @Override
733                public void run() {
734                    BluetoothPbapUtils.loadAllContacts(mContext,
735                            mSessionStatusHandler);
736                    mThreadLoadContacts = null;
737                }
738            };
739            mThreadLoadContacts = new Thread(r);
740            mThreadLoadContacts.start();
741        }
742    }
743
744    private void updateSecondaryVersion() {
745        if (mThreadUpdateSecVersionCounter == null) {
746            Runnable r = new Runnable() {
747                @Override
748                public void run() {
749                    BluetoothPbapUtils.updateSecondaryVersionCounter(mContext,
750                            mSessionStatusHandler);
751                    mThreadUpdateSecVersionCounter = null;
752                }
753            };
754            mThreadUpdateSecVersionCounter = new Thread(r);
755            mThreadUpdateSecVersionCounter.start();
756        }
757    }
758}
759