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