BluetoothPbapService.java revision 557b7d5218c907beaac2d7e28807dd4752db5f18
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.app.Notification;
36import android.app.NotificationChannel;
37import android.app.NotificationManager;
38import android.app.PendingIntent;
39import android.app.Service;
40import android.bluetooth.BluetoothAdapter;
41import android.bluetooth.BluetoothDevice;
42import android.bluetooth.BluetoothPbap;
43import android.bluetooth.BluetoothProfile;
44import android.bluetooth.BluetoothServerSocket;
45import android.bluetooth.BluetoothSocket;
46import android.bluetooth.BluetoothUuid;
47import android.bluetooth.IBluetoothPbap;
48import android.content.BroadcastReceiver;
49import android.content.Context;
50import android.content.Intent;
51import android.content.IntentFilter;
52import android.os.Handler;
53import android.os.IBinder;
54import android.os.Message;
55import android.os.PowerManager;
56import android.telephony.TelephonyManager;
57import android.text.TextUtils;
58import android.util.Log;
59
60import com.android.bluetooth.BluetoothObexTransport;
61import com.android.bluetooth.R;
62import com.android.bluetooth.Utils;
63import com.android.bluetooth.btservice.ProfileService;
64import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
65
66import java.io.IOException;
67
68import javax.obex.ServerSession;
69
70public class BluetoothPbapService extends ProfileService {
71    private static final String TAG = "BluetoothPbapService";
72
73    /**
74     * To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
75     * restart com.android.bluetooth process. only enable DEBUG log:
76     * "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and
77     * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE"
78     */
79
80    public static final boolean DEBUG = true;
81
82    public static final boolean VERBOSE = false;
83
84    /**
85     * Intent indicating incoming obex authentication request which is from
86     * PCE(Carkit)
87     */
88    public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall";
89
90    /**
91     * Intent indicating obex session key input complete by user which is sent
92     * from BluetoothPbapActivity
93     */
94    public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse";
95
96    /**
97     * Intent indicating user canceled obex authentication session key input
98     * which is sent from BluetoothPbapActivity
99     */
100    public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled";
101
102    /**
103     * Intent indicating timeout for user confirmation, which is sent to
104     * BluetoothPbapActivity
105     */
106    public static final String USER_CONFIRM_TIMEOUT_ACTION =
107            "com.android.bluetooth.pbap.userconfirmtimeout";
108
109    /**
110     * Intent Extra name indicating session key which is sent from
111     * BluetoothPbapActivity
112     */
113    public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
114
115    public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
116
117    public static final int MSG_SERVERSESSION_CLOSE = 5000;
118
119    public static final int MSG_SESSION_ESTABLISHED = 5001;
120
121    public static final int MSG_SESSION_DISCONNECTED = 5002;
122
123    public static final int MSG_OBEX_AUTH_CHALL = 5003;
124
125    public static final int MSG_ACQUIRE_WAKE_LOCK = 5004;
126
127    public static final int MSG_RELEASE_WAKE_LOCK = 5005;
128
129    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
130
131    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
132
133    private static final int START_LISTENER = 1;
134
135    private static final int USER_TIMEOUT = 2;
136
137    private static final int AUTH_TIMEOUT = 3;
138
139
140    private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
141
142    private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
143
144    // Ensure not conflict with Opp notification ID
145    private static final int NOTIFICATION_ID_ACCESS = -1000001;
146
147    private static final int NOTIFICATION_ID_AUTH = -1000002;
148
149    private static final String PBAP_NOTIFICATION_CHANNEL = "pbap_notification_channel";
150
151    private PowerManager.WakeLock mWakeLock = null;
152
153    private BluetoothAdapter mAdapter;
154
155    private SocketAcceptThread mAcceptThread = null;
156
157    private BluetoothPbapAuthenticator mAuth = null;
158
159    private BluetoothPbapObexServer mPbapServer;
160
161    private ServerSession mServerSession = null;
162
163    private BluetoothServerSocket mServerSocket = null;
164
165    private BluetoothSocket mConnSocket = null;
166
167    private BluetoothDevice mRemoteDevice = null;
168
169    private static String sLocalPhoneNum = null;
170
171    private static String sLocalPhoneName = null;
172
173    private static String sRemoteDeviceName = null;
174
175    private boolean mHasStarted = false;
176
177    private volatile boolean mInterrupted;
178
179    private int mState;
180
181    private int mStartId = -1;
182
183    private boolean mIsWaitingAuthorization = false;
184    private boolean mIsRegistered = false;
185
186    public BluetoothPbapService() {
187        mState = BluetoothPbap.STATE_DISCONNECTED;
188    }
189
190    // process the intent from receiver
191    private void parseIntent(final Intent intent) {
192        String action = intent.getAction();
193        if (action == null) return;             // Nothing to do
194        if (VERBOSE) Log.v(TAG, "action: " + action);
195
196        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
197        if (VERBOSE) Log.v(TAG, "state: " + state);
198
199        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
200            if (state == BluetoothAdapter.STATE_TURNING_OFF) {
201                // Send any pending timeout now, as this service will be destroyed.
202                if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) {
203                    mSessionStatusHandler.removeMessages(USER_TIMEOUT);
204                    mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
205                }
206                // Release all resources
207                closeService();
208                return;
209            } else if (state == BluetoothAdapter.STATE_ON) {
210                // start RFCOMM listener
211                mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
212            }
213        }
214
215        if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) {
216            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
217
218            if (mRemoteDevice == null) return;
219            if (DEBUG) Log.d(TAG,"ACL disconnected for "+ device);
220            if (mRemoteDevice.equals(device)) {
221                mSessionStatusHandler.removeMessages(USER_TIMEOUT);
222                mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
223            }
224            return;
225        }
226
227        if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
228            int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
229                                           BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
230
231            if ((!mIsWaitingAuthorization)
232                    || (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) {
233                // this reply is not for us
234                return;
235            }
236
237            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
238            mIsWaitingAuthorization = false;
239
240            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
241                                   BluetoothDevice.CONNECTION_ACCESS_NO)
242                    == BluetoothDevice.CONNECTION_ACCESS_YES) {
243                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
244                    boolean result = mRemoteDevice.setPhonebookAccessPermission(
245                            BluetoothDevice.ACCESS_ALLOWED);
246                    if (VERBOSE) {
247                        Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)=" + result);
248                    }
249                }
250                try {
251                    if (mConnSocket != null) {
252                        startObexServerSession();
253                    } else {
254                        stopObexServerSession();
255                    }
256                } catch (IOException ex) {
257                    Log.e(TAG, "Caught the error: " + ex.toString());
258                }
259            } else {
260                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
261                    boolean result = mRemoteDevice.setPhonebookAccessPermission(
262                            BluetoothDevice.ACCESS_REJECTED);
263                    if (VERBOSE) {
264                        Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)=" + result);
265                    }
266                }
267                stopObexServerSession();
268            }
269            return;
270        }
271
272        if (action.equals(AUTH_RESPONSE_ACTION)) {
273            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
274            notifyAuthKeyInput(sessionkey);
275        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
276            notifyAuthCancelled();
277        } else {
278            Log.w(TAG, "Unrecognized intent!");
279            return;
280        }
281
282        mSessionStatusHandler.removeMessages(USER_TIMEOUT);
283    }
284
285    private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
286        @Override
287        public void onReceive(Context context, Intent intent) {
288            parseIntent(intent);
289        }
290    };
291
292    private void startRfcommSocketListener() {
293        if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener");
294
295        if (mAcceptThread == null) {
296            mAcceptThread = new SocketAcceptThread();
297            mAcceptThread.setName("BluetoothPbapAcceptThread");
298            mAcceptThread.start();
299        }
300    }
301
302    private final boolean initSocket() {
303        if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
304
305        boolean initSocketOK = false;
306        final int CREATE_RETRY_TIME = 10;
307
308        // It's possible that create will fail in some cases. retry for 10 times
309        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
310            initSocketOK = true;
311            try {
312                // It is mandatory for PSE to support initiation of bonding and
313                // encryption.
314                mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
315                    ("OBEX Phonebook Access Server", BluetoothUuid.PBAP_PSE.getUuid());
316
317            } catch (IOException e) {
318                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
319                initSocketOK = false;
320            }
321            if (!initSocketOK) {
322                // Need to break out of this loop if BT is being turned off.
323                if (mAdapter == null) break;
324                int state = mAdapter.getState();
325                if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
326                    (state != BluetoothAdapter.STATE_ON)) {
327                    Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
328                    break;
329                }
330                try {
331                    if (VERBOSE) Log.v(TAG, "wait 300 ms");
332                    Thread.sleep(300);
333                } catch (InterruptedException e) {
334                    Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
335                    break;
336                }
337            } else {
338                break;
339            }
340        }
341
342        if (mInterrupted) {
343            initSocketOK = false;
344            // close server socket to avoid resource leakage
345            closeServerSocket();
346        }
347
348        if (initSocketOK) {
349            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
350
351        } else {
352            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
353        }
354        return initSocketOK;
355    }
356
357    private final synchronized void closeServerSocket() {
358        // exit SocketAcceptThread early
359        if (mServerSocket != null) {
360            try {
361                // this will cause mServerSocket.accept() return early with IOException
362                mServerSocket.close();
363                mServerSocket = null;
364            } catch (IOException ex) {
365                Log.e(TAG, "Close Server Socket error: " + ex);
366            }
367        }
368    }
369
370    private final synchronized void closeConnectionSocket() {
371        if (mConnSocket != null) {
372            try {
373                mConnSocket.close();
374                mConnSocket = null;
375            } catch (IOException e) {
376                Log.e(TAG, "Close Connection Socket error: " + e.toString());
377            }
378        }
379    }
380
381    private final void closeService() {
382        if (VERBOSE) Log.v(TAG, "Pbap Service closeService in");
383
384        // exit initSocket early
385        mInterrupted = true;
386        closeServerSocket();
387
388        if (mAcceptThread != null) {
389            try {
390                mAcceptThread.shutdown();
391                mAcceptThread.join();
392                mAcceptThread = null;
393            } catch (InterruptedException ex) {
394                Log.w(TAG, "mAcceptThread close error" + ex);
395            }
396        }
397
398        if (mWakeLock != null) {
399            mWakeLock.release();
400            mWakeLock = null;
401        }
402
403        if (mServerSession != null) {
404            mServerSession.close();
405            mServerSession = null;
406        }
407
408        closeConnectionSocket();
409
410        mHasStarted = false;
411        if (mStartId != -1 && stopSelfResult(mStartId)) {
412            if (VERBOSE) Log.v(TAG, "successfully stopped pbap service");
413            mStartId = -1;
414        }
415        if (VERBOSE) Log.v(TAG, "Pbap Service closeService out");
416    }
417
418    private final void startObexServerSession() throws IOException {
419        if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
420
421        // acquire the wakeLock before start Obex transaction thread
422        if (mWakeLock == null) {
423            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
424            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
425                    "StartingObexPbapTransaction");
426            mWakeLock.setReferenceCounted(false);
427            mWakeLock.acquire();
428        }
429        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
430        if (tm != null) {
431            sLocalPhoneNum = tm.getLine1Number();
432            sLocalPhoneName = tm.getLine1AlphaTag();
433            if (TextUtils.isEmpty(sLocalPhoneName)) {
434                sLocalPhoneName = this.getString(R.string.localPhoneName);
435            }
436        }
437
438        mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
439        synchronized (this) {
440            mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
441            mAuth.setChallenged(false);
442            mAuth.setCancelled(false);
443        }
444        BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
445        mServerSession = new ServerSession(transport, mPbapServer, mAuth);
446        setState(BluetoothPbap.STATE_CONNECTED);
447
448        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
449        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
450            .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
451
452        if (VERBOSE) {
453            Log.v(TAG, "startObexServerSession() success!");
454        }
455    }
456
457    private void stopObexServerSession() {
458        if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
459
460        mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
461        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
462        // Release the wake lock if obex transaction is over
463        if (mWakeLock != null) {
464            mWakeLock.release();
465            mWakeLock = null;
466        }
467
468        if (mServerSession != null) {
469            mServerSession.close();
470            mServerSession = null;
471        }
472
473        mAcceptThread = null;
474
475        closeConnectionSocket();
476
477        // Last obex transaction is finished, we start to listen for incoming
478        // connection again
479        if (mAdapter.isEnabled()) {
480            startRfcommSocketListener();
481        }
482        setState(BluetoothPbap.STATE_DISCONNECTED);
483    }
484
485    private void notifyAuthKeyInput(final String key) {
486        synchronized (mAuth) {
487            if (key != null) {
488                mAuth.setSessionKey(key);
489            }
490            mAuth.setChallenged(true);
491            mAuth.notify();
492        }
493    }
494
495    private void notifyAuthCancelled() {
496        synchronized (mAuth) {
497            mAuth.setCancelled(true);
498            mAuth.notify();
499        }
500    }
501
502    /**
503     * A thread that runs in the background waiting for remote rfcomm
504     * connect.Once a remote socket connected, this thread shall be
505     * shutdown.When the remote disconnect,this thread shall run again waiting
506     * for next request.
507     */
508    private class SocketAcceptThread extends Thread {
509
510        private boolean stopped = false;
511
512        @Override
513        public void run() {
514            BluetoothServerSocket serverSocket;
515            if (mServerSocket == null) {
516                if (!initSocket()) {
517                    return;
518                }
519            }
520
521            while (!stopped) {
522                try {
523                    if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
524                    serverSocket = mServerSocket;
525                    if (serverSocket == null) {
526                        Log.w(TAG, "mServerSocket is null");
527                        break;
528                    }
529                    mConnSocket = serverSocket.accept();
530                    if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
531
532                    synchronized (BluetoothPbapService.this) {
533                        if (mConnSocket == null) {
534                            Log.w(TAG, "mConnSocket is null");
535                            break;
536                        }
537                        mRemoteDevice = mConnSocket.getRemoteDevice();
538                    }
539                    if (mRemoteDevice == null) {
540                        Log.i(TAG, "getRemoteDevice() = null");
541                        break;
542                    }
543                    sRemoteDeviceName = mRemoteDevice.getName();
544                    // In case getRemoteName failed and return null
545                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
546                        sRemoteDeviceName = getString(R.string.defaultname);
547                    }
548                    int permission = mRemoteDevice.getPhonebookAccessPermission();
549                    if (VERBOSE) Log.v(TAG, "getPhonebookAccessPermission() = " + permission);
550
551                    if (permission == BluetoothDevice.ACCESS_ALLOWED) {
552                        try {
553                            if (VERBOSE) {
554                                Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
555                                        + " automatically as already allowed device");
556                            }
557                            startObexServerSession();
558                        } catch (IOException ex) {
559                            Log.e(TAG, "Caught exception starting obex server session"
560                                    + ex.toString());
561                        }
562                    } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
563                        if (VERBOSE) {
564                            Log.v(TAG, "incoming connection rejected from: " + sRemoteDeviceName
565                                    + " automatically as already rejected device");
566                        }
567                        stopObexServerSession();
568                    } else {  // permission == BluetoothDevice.ACCESS_UNKNOWN
569                        // Send an Intent to Settings app to ask user preference.
570                        Intent intent =
571                                new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
572                        intent.setPackage(getString(R.string.pairing_ui_package));
573                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
574                                        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
575                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
576                        intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
577                        intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, getName());
578
579                        mIsWaitingAuthorization = true;
580                        sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
581
582                        if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
583                                + sRemoteDeviceName);
584
585                        // In case car kit time out and try to use HFP for
586                        // phonebook
587                        // access, while UI still there waiting for user to
588                        // confirm
589                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
590                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
591                        // We will continue the process when we receive
592                        // BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app.
593                    }
594                    stopped = true; // job done ,close this thread;
595                } catch (IOException ex) {
596                    stopped=true;
597                    /*
598                    if (stopped) {
599                        break;
600                    }
601                    */
602                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
603                }
604            }
605        }
606
607        void shutdown() {
608            stopped = true;
609            interrupt();
610        }
611    }
612
613    private final Handler mSessionStatusHandler = new Handler() {
614        @Override
615        public void handleMessage(Message msg) {
616            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
617
618            switch (msg.what) {
619                case START_LISTENER:
620                    if (mAdapter.isEnabled()) {
621                        startRfcommSocketListener();
622                    }
623                    break;
624                case USER_TIMEOUT:
625                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
626                    intent.setPackage(getString(R.string.pairing_ui_package));
627                    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
628                    intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
629                                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
630                    sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
631                    mIsWaitingAuthorization = false;
632                    stopObexServerSession();
633                    break;
634                case AUTH_TIMEOUT:
635                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
636                    sendBroadcast(i);
637                    removePbapNotification(NOTIFICATION_ID_AUTH);
638                    notifyAuthCancelled();
639                    break;
640                case MSG_SERVERSESSION_CLOSE:
641                    stopObexServerSession();
642                    break;
643                case MSG_SESSION_ESTABLISHED:
644                    break;
645                case MSG_SESSION_DISCONNECTED:
646                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
647                    break;
648                case MSG_OBEX_AUTH_CHALL:
649                    createPbapNotification(AUTH_CHALL_ACTION);
650                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
651                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
652                    break;
653                case MSG_ACQUIRE_WAKE_LOCK:
654                    if (mWakeLock == null) {
655                        PowerManager pm = (PowerManager)getSystemService(
656                                          Context.POWER_SERVICE);
657                        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
658                                    "StartingObexPbapTransaction");
659                        mWakeLock.setReferenceCounted(false);
660                        mWakeLock.acquire();
661                        Log.w(TAG, "Acquire Wake Lock");
662                    }
663                    mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
664                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
665                      .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
666                    break;
667                case MSG_RELEASE_WAKE_LOCK:
668                    if (mWakeLock != null) {
669                        mWakeLock.release();
670                        mWakeLock = null;
671                        Log.w(TAG, "Release Wake Lock");
672                    }
673                    break;
674                default:
675                    break;
676            }
677        }
678    };
679
680    private void setState(int state) {
681        setState(state, BluetoothPbap.RESULT_SUCCESS);
682    }
683
684    private synchronized void setState(int state, int result) {
685        if (state != mState) {
686            if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
687                    + result);
688            int prevState = mState;
689            mState = state;
690            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
691            intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, prevState);
692            intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
693            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
694            sendBroadcast(intent, BLUETOOTH_PERM);
695        }
696    }
697
698    protected int getState() {
699        return mState;
700    }
701
702    protected BluetoothDevice getRemoteDevice() {
703        return mRemoteDevice;
704    }
705
706    private void createPbapNotification(String action) {
707
708        NotificationManager nm = (NotificationManager)
709            getSystemService(Context.NOTIFICATION_SERVICE);
710        NotificationChannel notificationChannel = new NotificationChannel(PBAP_NOTIFICATION_CHANNEL,
711                getString(R.string.pbap_notification_group), NotificationManager.IMPORTANCE_HIGH);
712        nm.createNotificationChannel(notificationChannel);
713
714        // Create an intent triggered by clicking on the status icon.
715        Intent clickIntent = new Intent();
716        clickIntent.setClass(this, BluetoothPbapActivity.class);
717        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
718        clickIntent.setAction(action);
719
720        // Create an intent triggered by clicking on the
721        // "Clear All Notifications" button
722        Intent deleteIntent = new Intent();
723        deleteIntent.setClass(this, BluetoothPbapService.class);
724        deleteIntent.setAction(AUTH_CANCELLED_ACTION);
725
726        String name = getRemoteDeviceName();
727
728        if (action.equals(AUTH_CHALL_ACTION)) {
729            Notification notification =
730                    new Notification.Builder(this, PBAP_NOTIFICATION_CHANNEL)
731                            .setWhen(System.currentTimeMillis())
732                            .setContentTitle(getString(R.string.auth_notif_title))
733                            .setContentText(getString(R.string.auth_notif_message, name))
734                            .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
735                            .setTicker(getString(R.string.auth_notif_ticker))
736                            .setColor(getResources().getColor(
737                                    com.android.internal.R.color.system_notification_accent_color,
738                                    this.getTheme()))
739                            .setFlag(Notification.FLAG_AUTO_CANCEL, true)
740                            .setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
741                            .setDefaults(Notification.DEFAULT_SOUND)
742                            .setContentIntent(PendingIntent.getActivity(this, 0, clickIntent, 0))
743                            .setDeleteIntent(PendingIntent.getBroadcast(this, 0, deleteIntent, 0))
744                            .build();
745            nm.notify(NOTIFICATION_ID_AUTH, notification);
746        }
747    }
748
749    private void removePbapNotification(int id) {
750        NotificationManager nm = (NotificationManager)
751            getSystemService(Context.NOTIFICATION_SERVICE);
752        nm.cancel(id);
753    }
754
755    public static String getLocalPhoneNum() {
756        return sLocalPhoneNum;
757    }
758
759    public static String getLocalPhoneName() {
760        return sLocalPhoneName;
761    }
762
763    public static String getRemoteDeviceName() {
764        return sRemoteDeviceName;
765    }
766
767    @Override
768    protected IProfileServiceBinder initBinder() {
769        return new PbapBinder(this);
770    }
771
772    @Override
773    protected boolean start() {
774        Log.v(TAG, "start()");
775        IntentFilter filter = new IntentFilter();
776        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
777        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
778        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
779        filter.addAction(AUTH_RESPONSE_ACTION);
780        filter.addAction(AUTH_CANCELLED_ACTION);
781
782        try {
783            registerReceiver(mPbapReceiver, filter);
784            mIsRegistered = true;
785        } catch (Exception e) {
786            Log.w(TAG, "Unable to register pbap receiver", e);
787        }
788        mInterrupted = false;
789        BluetoothPbapConfig.init(this);
790        mAdapter = BluetoothAdapter.getDefaultAdapter();
791        return true;
792    }
793
794    @Override
795    protected boolean stop() {
796        Log.v(TAG, "stop()");
797        if (!mIsRegistered) {
798            Log.i(TAG, "Avoid unregister when receiver it is not registered");
799            return true;
800        }
801        try {
802            mIsRegistered = false;
803            unregisterReceiver(mPbapReceiver);
804        } catch (Exception e) {
805            Log.w(TAG, "Unable to unregister pbap receiver", e);
806        }
807        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
808        closeService();
809        if (mSessionStatusHandler != null) {
810            mSessionStatusHandler.removeCallbacksAndMessages(null);
811        }
812        return true;
813    }
814
815    protected void disconnect() {
816        synchronized (this) {
817            if (mState == BluetoothPbap.STATE_CONNECTED) {
818                if (mServerSession != null) {
819                    mServerSession.close();
820                    mServerSession = null;
821                }
822
823                closeConnectionSocket();
824
825                setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
826            }
827        }
828    }
829
830    // Has to be a static class or a memory leak can occur.
831    private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder {
832        private BluetoothPbapService mService;
833
834        private BluetoothPbapService getService(String perm) {
835            if (!Utils.checkCaller()) {
836                Log.w(TAG, "not allowed for non-active user");
837                return null;
838            }
839            if (mService != null && mService.isAvailable()) {
840                mService.enforceCallingOrSelfPermission(perm, "Need " + perm + " permission");
841                return mService;
842            }
843            return null;
844        }
845
846        PbapBinder(BluetoothPbapService service) {
847            Log.v(TAG, "PbapBinder()");
848            mService = service;
849        }
850
851        public boolean cleanup() {
852            mService = null;
853            return true;
854        }
855
856        public int getState() {
857            if (DEBUG) Log.d(TAG, "getState = " + mService.getState());
858            BluetoothPbapService service = getService(BLUETOOTH_PERM);
859            if (service == null) return BluetoothPbap.STATE_DISCONNECTED;
860
861            return service.getState();
862        }
863
864        public BluetoothDevice getClient() {
865            if (DEBUG) Log.d(TAG, "getClient = " + mService.getRemoteDevice());
866            BluetoothPbapService service = getService(BLUETOOTH_PERM);
867            if (service == null) return null;
868            return service.getRemoteDevice();
869        }
870
871        public boolean isConnected(BluetoothDevice device) {
872            if (DEBUG) Log.d(TAG, "isConnected " + device);
873            BluetoothPbapService service = getService(BLUETOOTH_PERM);
874            if (service == null) return false;
875            return service.getState() == BluetoothPbap.STATE_CONNECTED
876                    && service.getRemoteDevice().equals(device);
877        }
878
879        public boolean connect(BluetoothDevice device) {
880            BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
881            return false;
882        }
883
884        public void disconnect() {
885            if (DEBUG) Log.d(TAG, "disconnect");
886            BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
887            if (service == null) return;
888            service.disconnect();
889        }
890    }
891}
892