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