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