BluetoothPbapService.java revision e061a83b8cbd872cd9f0fea33717aa57112e6228
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_CONNECTION_ACCESS_REPLY)) {
260            int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
261                                           BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
262
263            if ((!isWaitingAuthorization) ||
264                (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) {
265                // this reply is not for us
266                return;
267            }
268
269            isWaitingAuthorization = false;
270
271            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
272                                   BluetoothDevice.CONNECTION_ACCESS_NO) ==
273                BluetoothDevice.CONNECTION_ACCESS_YES) {
274
275                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
276                    boolean result = mRemoteDevice.setTrust(true);
277                    if (VERBOSE) Log.v(TAG, "setTrust() result=" + result);
278                }
279                try {
280                    if (mConnSocket != null) {
281                        startObexServerSession();
282                    } else {
283                        stopObexServerSession();
284                    }
285                } catch (IOException ex) {
286                    Log.e(TAG, "Caught the error: " + ex.toString());
287                }
288            } else {
289                stopObexServerSession();
290            }
291        } else if (action.equals(AUTH_RESPONSE_ACTION)) {
292            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
293            notifyAuthKeyInput(sessionkey);
294        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
295            notifyAuthCancelled();
296        } else {
297            removeTimeoutMsg = false;
298        }
299
300        if (removeTimeoutMsg) {
301            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
302        }
303    }
304
305    @Override
306    public void onDestroy() {
307        if (VERBOSE) Log.v(TAG, "Pbap Service onDestroy");
308
309        super.onDestroy();
310        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
311        closeService();
312        if(mSessionStatusHandler != null) {
313            mSessionStatusHandler.removeCallbacksAndMessages(null);
314        }
315    }
316
317    @Override
318    public IBinder onBind(Intent intent) {
319        if (VERBOSE) Log.v(TAG, "Pbap Service onBind");
320        return mBinder;
321    }
322
323    private void startRfcommSocketListener() {
324        if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener");
325
326        if (mAcceptThread == null) {
327            mAcceptThread = new SocketAcceptThread();
328            mAcceptThread.setName("BluetoothPbapAcceptThread");
329            mAcceptThread.start();
330        }
331    }
332
333    private final boolean initSocket() {
334        if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
335
336        boolean initSocketOK = false;
337        final int CREATE_RETRY_TIME = 10;
338
339        // It's possible that create will fail in some cases. retry for 10 times
340        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
341            initSocketOK = true;
342            try {
343                // It is mandatory for PSE to support initiation of bonding and
344                // encryption.
345                mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
346                    ("OBEX Phonebook Access Server", BluetoothUuid.PBAP_PSE.getUuid());
347
348            } catch (IOException e) {
349                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
350                initSocketOK = false;
351            }
352            if (!initSocketOK) {
353                // Need to break out of this loop if BT is being turned off.
354                if (mAdapter == null) break;
355                int state = mAdapter.getState();
356                if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
357                    (state != BluetoothAdapter.STATE_ON)) {
358                    Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
359                    break;
360                }
361                try {
362                    if (VERBOSE) Log.v(TAG, "wait 300 ms");
363                    Thread.sleep(300);
364                } catch (InterruptedException e) {
365                    Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
366                    break;
367                }
368            } else {
369                break;
370            }
371        }
372
373        if (mInterrupted) {
374            initSocketOK = false;
375            // close server socket to avoid resource leakage
376            closeServerSocket();
377        }
378
379        if (initSocketOK) {
380            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
381
382        } else {
383            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
384        }
385        return initSocketOK;
386    }
387
388    private final synchronized void closeServerSocket() {
389        // exit SocketAcceptThread early
390        if (mServerSocket != null) {
391            try {
392                // this will cause mServerSocket.accept() return early with IOException
393                mServerSocket.close();
394                mServerSocket = null;
395            } catch (IOException ex) {
396                Log.e(TAG, "Close Server Socket error: " + ex);
397            }
398        }
399    }
400
401    private final synchronized void closeConnectionSocket() {
402        if (mConnSocket != null) {
403            try {
404                mConnSocket.close();
405                mConnSocket = null;
406            } catch (IOException e) {
407                Log.e(TAG, "Close Connection Socket error: " + e.toString());
408            }
409        }
410    }
411
412    private final void closeService() {
413        if (VERBOSE) Log.v(TAG, "Pbap Service closeService in");
414
415        // exit initSocket early
416        mInterrupted = true;
417        closeServerSocket();
418
419        if (mAcceptThread != null) {
420            try {
421                mAcceptThread.shutdown();
422                mAcceptThread.join();
423                mAcceptThread = null;
424            } catch (InterruptedException ex) {
425                Log.w(TAG, "mAcceptThread close error" + ex);
426            }
427        }
428
429        if (mWakeLock != null) {
430            mWakeLock.release();
431            mWakeLock = null;
432        }
433
434        if (mServerSession != null) {
435            mServerSession.close();
436            mServerSession = null;
437        }
438
439        closeConnectionSocket();
440
441        mHasStarted = false;
442        if (mStartId != -1 && stopSelfResult(mStartId)) {
443            if (VERBOSE) Log.v(TAG, "successfully stopped pbap service");
444            mStartId = -1;
445        }
446        if (VERBOSE) Log.v(TAG, "Pbap Service closeService out");
447    }
448
449    private final void startObexServerSession() throws IOException {
450        if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
451
452        // acquire the wakeLock before start Obex transaction thread
453        if (mWakeLock == null) {
454            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
455            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
456                    "StartingObexPbapTransaction");
457            mWakeLock.setReferenceCounted(false);
458            mWakeLock.acquire();
459        }
460        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
461        if (tm != null) {
462            sLocalPhoneNum = tm.getLine1Number();
463            sLocalPhoneName = tm.getLine1AlphaTag();
464            if (TextUtils.isEmpty(sLocalPhoneName)) {
465                sLocalPhoneName = this.getString(R.string.localPhoneName);
466            }
467        }
468
469        mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
470        synchronized (this) {
471            mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
472            mAuth.setChallenged(false);
473            mAuth.setCancelled(false);
474        }
475        BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket);
476        mServerSession = new ServerSession(transport, mPbapServer, mAuth);
477        setState(BluetoothPbap.STATE_CONNECTED);
478
479        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
480        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
481            .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
482
483        if (VERBOSE) {
484            Log.v(TAG, "startObexServerSession() success!");
485        }
486    }
487
488    private void stopObexServerSession() {
489        if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
490
491        mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
492        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
493        // Release the wake lock if obex transaction is over
494        if (mWakeLock != null) {
495            mWakeLock.release();
496            mWakeLock = null;
497        }
498
499        if (mServerSession != null) {
500            mServerSession.close();
501            mServerSession = null;
502        }
503
504        mAcceptThread = null;
505
506        closeConnectionSocket();
507
508        // Last obex transaction is finished, we start to listen for incoming
509        // connection again
510        if (mAdapter.isEnabled()) {
511            startRfcommSocketListener();
512        }
513        setState(BluetoothPbap.STATE_DISCONNECTED);
514    }
515
516    private void notifyAuthKeyInput(final String key) {
517        synchronized (mAuth) {
518            if (key != null) {
519                mAuth.setSessionKey(key);
520            }
521            mAuth.setChallenged(true);
522            mAuth.notify();
523        }
524    }
525
526    private void notifyAuthCancelled() {
527        synchronized (mAuth) {
528            mAuth.setCancelled(true);
529            mAuth.notify();
530        }
531    }
532
533    /**
534     * A thread that runs in the background waiting for remote rfcomm
535     * connect.Once a remote socket connected, this thread shall be
536     * shutdown.When the remote disconnect,this thread shall run again waiting
537     * for next request.
538     */
539    private class SocketAcceptThread extends Thread {
540
541        private boolean stopped = false;
542
543        @Override
544        public void run() {
545            BluetoothServerSocket serverSocket;
546            if (mServerSocket == null) {
547                if (!initSocket()) {
548                    return;
549                }
550            }
551
552            while (!stopped) {
553                try {
554                    if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
555                    serverSocket = mServerSocket;
556                    if (serverSocket == null) {
557                        Log.w(TAG, "mServerSocket is null");
558                        break;
559                    }
560                    mConnSocket = serverSocket.accept();
561                    if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
562
563                    synchronized (BluetoothPbapService.this) {
564                        if (mConnSocket == null) {
565                            Log.w(TAG, "mConnSocket is null");
566                            break;
567                        }
568                        mRemoteDevice = mConnSocket.getRemoteDevice();
569                    }
570                    if (mRemoteDevice == null) {
571                        Log.i(TAG, "getRemoteDevice() = null");
572                        break;
573                    }
574                    sRemoteDeviceName = mRemoteDevice.getName();
575                    // In case getRemoteName failed and return null
576                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
577                        sRemoteDeviceName = getString(R.string.defaultname);
578                    }
579                    boolean trust = mRemoteDevice.getTrustState();
580                    if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
581
582                    if (trust) {
583                        try {
584                            if (VERBOSE) Log.v(TAG, "incoming connection accepted from: "
585                                + sRemoteDeviceName + " automatically as trusted device");
586                            startObexServerSession();
587                        } catch (IOException ex) {
588                            Log.e(TAG, "catch exception starting obex server session"
589                                    + ex.toString());
590                        }
591                    } else {
592                        Intent intent = new
593                            Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
594                        intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
595                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
596                                        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
597                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
598                        intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
599                        intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME,
600                                        BluetoothPbapReceiver.class.getName());
601
602                        isWaitingAuthorization = true;
603                        sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
604
605                        if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
606                                + sRemoteDeviceName);
607
608                        // In case car kit time out and try to use HFP for
609                        // phonebook
610                        // access, while UI still there waiting for user to
611                        // confirm
612                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
613                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
614                    }
615                    stopped = true; // job done ,close this thread;
616                } catch (IOException ex) {
617                    stopped=true;
618                    /*
619                    if (stopped) {
620                        break;
621                    }
622                    */
623                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
624                }
625            }
626        }
627
628        void shutdown() {
629            stopped = true;
630            interrupt();
631        }
632    }
633
634    private final Handler mSessionStatusHandler = new Handler() {
635        @Override
636        public void handleMessage(Message msg) {
637            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
638
639            switch (msg.what) {
640                case START_LISTENER:
641                    if (mAdapter.isEnabled()) {
642                        startRfcommSocketListener();
643                    } else {
644                        closeService();// release all resources
645                    }
646                    break;
647                case USER_TIMEOUT:
648                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
649                    intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
650                    intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
651                                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
652                    sendBroadcast(intent);
653                    isWaitingAuthorization = false;
654                    stopObexServerSession();
655                    break;
656                case AUTH_TIMEOUT:
657                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
658                    sendBroadcast(i);
659                    removePbapNotification(NOTIFICATION_ID_AUTH);
660                    notifyAuthCancelled();
661                    break;
662                case MSG_SERVERSESSION_CLOSE:
663                    stopObexServerSession();
664                    break;
665                case MSG_SESSION_ESTABLISHED:
666                    break;
667                case MSG_SESSION_DISCONNECTED:
668                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
669                    break;
670                case MSG_OBEX_AUTH_CHALL:
671                    createPbapNotification(AUTH_CHALL_ACTION);
672                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
673                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
674                    break;
675                case MSG_ACQUIRE_WAKE_LOCK:
676                    if (mWakeLock == null) {
677                        PowerManager pm = (PowerManager)getSystemService(
678                                          Context.POWER_SERVICE);
679                        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
680                                    "StartingObexPbapTransaction");
681                        mWakeLock.setReferenceCounted(false);
682                        mWakeLock.acquire();
683                        Log.w(TAG, "Acquire Wake Lock");
684                    }
685                    mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
686                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
687                      .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
688                    break;
689                case MSG_RELEASE_WAKE_LOCK:
690                    if (mWakeLock != null) {
691                        mWakeLock.release();
692                        mWakeLock = null;
693                        Log.w(TAG, "Release Wake Lock");
694                    }
695                    break;
696                default:
697                    break;
698            }
699        }
700    };
701
702    private void setState(int state) {
703        setState(state, BluetoothPbap.RESULT_SUCCESS);
704    }
705
706    private synchronized void setState(int state, int result) {
707        if (state != mState) {
708            if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
709                    + result);
710            int prevState = mState;
711            mState = state;
712            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
713            intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, prevState);
714            intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
715            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
716            sendBroadcast(intent, BLUETOOTH_PERM);
717            AdapterService s = AdapterService.getAdapterService();
718            if (s != null) {
719                s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.PBAP,
720                        mState, prevState);
721            }
722        }
723    }
724
725    private void createPbapNotification(String action) {
726
727        NotificationManager nm = (NotificationManager)
728            getSystemService(Context.NOTIFICATION_SERVICE);
729
730        // Create an intent triggered by clicking on the status icon.
731        Intent clickIntent = new Intent();
732        clickIntent.setClass(this, BluetoothPbapActivity.class);
733        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
734        clickIntent.setAction(action);
735
736        // Create an intent triggered by clicking on the
737        // "Clear All Notifications" button
738        Intent deleteIntent = new Intent();
739        deleteIntent.setClass(this, BluetoothPbapReceiver.class);
740
741        Notification notification = null;
742        String name = getRemoteDeviceName();
743
744        if (action.equals(AUTH_CHALL_ACTION)) {
745            deleteIntent.setAction(AUTH_CANCELLED_ACTION);
746            notification = new Notification(android.R.drawable.stat_sys_data_bluetooth,
747                getString(R.string.auth_notif_ticker), System.currentTimeMillis());
748            notification.setLatestEventInfo(this, getString(R.string.auth_notif_title),
749                    getString(R.string.auth_notif_message, name), PendingIntent
750                            .getActivity(this, 0, clickIntent, 0));
751
752            notification.flags |= Notification.FLAG_AUTO_CANCEL;
753            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
754            notification.defaults = Notification.DEFAULT_SOUND;
755            notification.deleteIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, 0);
756            nm.notify(NOTIFICATION_ID_AUTH, notification);
757        }
758    }
759
760    private void removePbapNotification(int id) {
761        NotificationManager nm = (NotificationManager)
762            getSystemService(Context.NOTIFICATION_SERVICE);
763        nm.cancel(id);
764    }
765
766    public static String getLocalPhoneNum() {
767        return sLocalPhoneNum;
768    }
769
770    public static String getLocalPhoneName() {
771        return sLocalPhoneName;
772    }
773
774    public static String getRemoteDeviceName() {
775        return sRemoteDeviceName;
776    }
777
778    /**
779     * Handlers for incoming service calls
780     */
781    private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() {
782        public int getState() {
783            if (DEBUG) Log.d(TAG, "getState " + mState);
784
785            if (!Utils.checkCaller()) {
786                Log.w(TAG,"getState(): not allowed for non-active user");
787                return BluetoothPbap.STATE_DISCONNECTED;
788            }
789
790            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
791            return mState;
792        }
793
794        public BluetoothDevice getClient() {
795            if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice);
796
797            if (!Utils.checkCaller()) {
798                Log.w(TAG,"getClient(): not allowed for non-active user");
799                return null;
800            }
801
802            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
803            if (mState == BluetoothPbap.STATE_DISCONNECTED) {
804                return null;
805            }
806            return mRemoteDevice;
807        }
808
809        public boolean isConnected(BluetoothDevice device) {
810            if (!Utils.checkCaller()) {
811                Log.w(TAG,"isConnected(): not allowed for non-active user");
812                return false;
813            }
814
815            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
816            return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device);
817        }
818
819        public boolean connect(BluetoothDevice device) {
820            if (!Utils.checkCaller()) {
821                Log.w(TAG,"connect(): not allowed for non-active user");
822                return false;
823            }
824
825            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
826                    "Need BLUETOOTH_ADMIN permission");
827            return false;
828        }
829
830        public void disconnect() {
831            if (DEBUG) Log.d(TAG, "disconnect");
832
833            if (!Utils.checkCaller()) {
834                Log.w(TAG,"disconnect(): not allowed for non-active user");
835                return;
836            }
837
838            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
839                    "Need BLUETOOTH_ADMIN permission");
840            synchronized (BluetoothPbapService.this) {
841                switch (mState) {
842                    case BluetoothPbap.STATE_CONNECTED:
843                        if (mServerSession != null) {
844                            mServerSession.close();
845                            mServerSession = null;
846                        }
847
848                        closeConnectionSocket();
849
850                        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
851                        break;
852                    default:
853                        break;
854                }
855            }
856        }
857    };
858}
859