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