BluetoothPbapService.java revision 94e5a24c73e66e3dafd073c7a97904b1c071a787
1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.android.bluetooth.pbap;
34
35import android.app.Notification;
36import android.app.NotificationManager;
37import android.app.PendingIntent;
38import android.app.Service;
39import android.bluetooth.BluetoothAdapter;
40import android.bluetooth.BluetoothDevice;
41import android.bluetooth.BluetoothPbap;
42import android.bluetooth.BluetoothProfile;
43import android.bluetooth.BluetoothServerSocket;
44import android.bluetooth.BluetoothSocket;
45import android.bluetooth.BluetoothUuid;
46import android.bluetooth.IBluetoothPbap;
47import android.content.BroadcastReceiver;
48import android.content.Context;
49import android.content.Intent;
50import android.content.IntentFilter;
51import android.os.Handler;
52import android.os.IBinder;
53import android.os.Message;
54import android.os.PowerManager;
55import android.telephony.TelephonyManager;
56import android.text.TextUtils;
57import android.util.Log;
58
59import com.android.bluetooth.BluetoothObexTransport;
60import com.android.bluetooth.R;
61import com.android.bluetooth.Utils;
62import com.android.bluetooth.btservice.ProfileService;
63import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
64
65import java.io.IOException;
66
67import javax.obex.ServerSession;
68
69public class BluetoothPbapService extends ProfileService {
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 boolean mIsWaitingAuthorization = false;
181    private boolean mIsRegistered = false;
182
183    public BluetoothPbapService() {
184        mState = BluetoothPbap.STATE_DISCONNECTED;
185    }
186
187    // process the intent from receiver
188    private void parseIntent(final Intent intent) {
189        String action = intent.getAction();
190        if (action == null) return;             // Nothing to do
191        if (VERBOSE) Log.v(TAG, "action: " + action);
192
193        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
194        if (VERBOSE) Log.v(TAG, "state: " + state);
195
196        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
197            if (state == BluetoothAdapter.STATE_TURNING_OFF) {
198                // Send any pending timeout now, as this service will be destroyed.
199                if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) {
200                    mSessionStatusHandler.removeMessages(USER_TIMEOUT);
201                    mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
202                }
203                // Release all resources
204                closeService();
205                return;
206            } else if (state == BluetoothAdapter.STATE_ON) {
207                // start RFCOMM listener
208                mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
209            }
210        }
211
212        if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) {
213            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
214
215            if (mRemoteDevice == null) return;
216            if (DEBUG) Log.d(TAG,"ACL disconnected for "+ device);
217            if (mRemoteDevice.equals(device)) {
218                mSessionStatusHandler.removeMessages(USER_TIMEOUT);
219                mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
220            }
221            return;
222        }
223
224        if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
225            int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
226                                           BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
227
228            if ((!mIsWaitingAuthorization)
229                    || (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) {
230                // this reply is not for us
231                return;
232            }
233
234            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
235            mIsWaitingAuthorization = false;
236
237            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
238                                   BluetoothDevice.CONNECTION_ACCESS_NO)
239                    == BluetoothDevice.CONNECTION_ACCESS_YES) {
240                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
241                    boolean result = mRemoteDevice.setPhonebookAccessPermission(
242                            BluetoothDevice.ACCESS_ALLOWED);
243                    if (VERBOSE) {
244                        Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)=" + result);
245                    }
246                }
247                try {
248                    if (mConnSocket != null) {
249                        startObexServerSession();
250                    } else {
251                        stopObexServerSession();
252                    }
253                } catch (IOException ex) {
254                    Log.e(TAG, "Caught the error: " + ex.toString());
255                }
256            } else {
257                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
258                    boolean result = mRemoteDevice.setPhonebookAccessPermission(
259                            BluetoothDevice.ACCESS_REJECTED);
260                    if (VERBOSE) {
261                        Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)=" + result);
262                    }
263                }
264                stopObexServerSession();
265            }
266            return;
267        }
268
269        if (action.equals(AUTH_RESPONSE_ACTION)) {
270            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
271            notifyAuthKeyInput(sessionkey);
272        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
273            notifyAuthCancelled();
274        } else {
275            Log.w(TAG, "Unrecognized intent!");
276            return;
277        }
278
279        mSessionStatusHandler.removeMessages(USER_TIMEOUT);
280    }
281
282    private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
283        @Override
284        public void onReceive(Context context, Intent intent) {
285            parseIntent(intent);
286        }
287    };
288
289    private void startRfcommSocketListener() {
290        if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener");
291
292        if (mAcceptThread == null) {
293            mAcceptThread = new SocketAcceptThread();
294            mAcceptThread.setName("BluetoothPbapAcceptThread");
295            mAcceptThread.start();
296        }
297    }
298
299    private final boolean initSocket() {
300        if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
301
302        boolean initSocketOK = false;
303        final int CREATE_RETRY_TIME = 10;
304
305        // It's possible that create will fail in some cases. retry for 10 times
306        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
307            initSocketOK = true;
308            try {
309                // It is mandatory for PSE to support initiation of bonding and
310                // encryption.
311                mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
312                    ("OBEX Phonebook Access Server", BluetoothUuid.PBAP_PSE.getUuid());
313
314            } catch (IOException e) {
315                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
316                initSocketOK = false;
317            }
318            if (!initSocketOK) {
319                // Need to break out of this loop if BT is being turned off.
320                if (mAdapter == null) break;
321                int state = mAdapter.getState();
322                if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
323                    (state != BluetoothAdapter.STATE_ON)) {
324                    Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
325                    break;
326                }
327                try {
328                    if (VERBOSE) Log.v(TAG, "wait 300 ms");
329                    Thread.sleep(300);
330                } catch (InterruptedException e) {
331                    Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
332                    break;
333                }
334            } else {
335                break;
336            }
337        }
338
339        if (mInterrupted) {
340            initSocketOK = false;
341            // close server socket to avoid resource leakage
342            closeServerSocket();
343        }
344
345        if (initSocketOK) {
346            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
347
348        } else {
349            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
350        }
351        return initSocketOK;
352    }
353
354    private final synchronized void closeServerSocket() {
355        // exit SocketAcceptThread early
356        if (mServerSocket != null) {
357            try {
358                // this will cause mServerSocket.accept() return early with IOException
359                mServerSocket.close();
360                mServerSocket = null;
361            } catch (IOException ex) {
362                Log.e(TAG, "Close Server Socket error: " + ex);
363            }
364        }
365    }
366
367    private final synchronized void closeConnectionSocket() {
368        if (mConnSocket != null) {
369            try {
370                mConnSocket.close();
371                mConnSocket = null;
372            } catch (IOException e) {
373                Log.e(TAG, "Close Connection Socket error: " + e.toString());
374            }
375        }
376    }
377
378    private final void closeService() {
379        if (VERBOSE) Log.v(TAG, "Pbap Service closeService in");
380
381        // exit initSocket early
382        mInterrupted = true;
383        closeServerSocket();
384
385        if (mAcceptThread != null) {
386            try {
387                mAcceptThread.shutdown();
388                mAcceptThread.join();
389                mAcceptThread = null;
390            } catch (InterruptedException ex) {
391                Log.w(TAG, "mAcceptThread close error" + ex);
392            }
393        }
394
395        if (mWakeLock != null) {
396            mWakeLock.release();
397            mWakeLock = null;
398        }
399
400        if (mServerSession != null) {
401            mServerSession.close();
402            mServerSession = null;
403        }
404
405        closeConnectionSocket();
406
407        mHasStarted = false;
408        if (mStartId != -1 && stopSelfResult(mStartId)) {
409            if (VERBOSE) Log.v(TAG, "successfully stopped pbap service");
410            mStartId = -1;
411        }
412        if (VERBOSE) Log.v(TAG, "Pbap Service closeService out");
413    }
414
415    private final void startObexServerSession() throws IOException {
416        if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
417
418        // acquire the wakeLock before start Obex transaction thread
419        if (mWakeLock == null) {
420            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
421            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
422                    "StartingObexPbapTransaction");
423            mWakeLock.setReferenceCounted(false);
424            mWakeLock.acquire();
425        }
426        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
427        if (tm != null) {
428            sLocalPhoneNum = tm.getLine1Number();
429            sLocalPhoneName = tm.getLine1AlphaTag();
430            if (TextUtils.isEmpty(sLocalPhoneName)) {
431                sLocalPhoneName = this.getString(R.string.localPhoneName);
432            }
433        }
434
435        mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
436        synchronized (this) {
437            mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
438            mAuth.setChallenged(false);
439            mAuth.setCancelled(false);
440        }
441        BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
442        mServerSession = new ServerSession(transport, mPbapServer, mAuth);
443        setState(BluetoothPbap.STATE_CONNECTED);
444
445        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
446        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
447            .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
448
449        if (VERBOSE) {
450            Log.v(TAG, "startObexServerSession() success!");
451        }
452    }
453
454    private void stopObexServerSession() {
455        if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
456
457        mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
458        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
459        // Release the wake lock if obex transaction is over
460        if (mWakeLock != null) {
461            mWakeLock.release();
462            mWakeLock = null;
463        }
464
465        if (mServerSession != null) {
466            mServerSession.close();
467            mServerSession = null;
468        }
469
470        mAcceptThread = null;
471
472        closeConnectionSocket();
473
474        // Last obex transaction is finished, we start to listen for incoming
475        // connection again
476        if (mAdapter.isEnabled()) {
477            startRfcommSocketListener();
478        }
479        setState(BluetoothPbap.STATE_DISCONNECTED);
480    }
481
482    private void notifyAuthKeyInput(final String key) {
483        synchronized (mAuth) {
484            if (key != null) {
485                mAuth.setSessionKey(key);
486            }
487            mAuth.setChallenged(true);
488            mAuth.notify();
489        }
490    }
491
492    private void notifyAuthCancelled() {
493        synchronized (mAuth) {
494            mAuth.setCancelled(true);
495            mAuth.notify();
496        }
497    }
498
499    /**
500     * A thread that runs in the background waiting for remote rfcomm
501     * connect.Once a remote socket connected, this thread shall be
502     * shutdown.When the remote disconnect,this thread shall run again waiting
503     * for next request.
504     */
505    private class SocketAcceptThread extends Thread {
506
507        private boolean stopped = false;
508
509        @Override
510        public void run() {
511            BluetoothServerSocket serverSocket;
512            if (mServerSocket == null) {
513                if (!initSocket()) {
514                    return;
515                }
516            }
517
518            while (!stopped) {
519                try {
520                    if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
521                    serverSocket = mServerSocket;
522                    if (serverSocket == null) {
523                        Log.w(TAG, "mServerSocket is null");
524                        break;
525                    }
526                    mConnSocket = serverSocket.accept();
527                    if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
528
529                    synchronized (BluetoothPbapService.this) {
530                        if (mConnSocket == null) {
531                            Log.w(TAG, "mConnSocket is null");
532                            break;
533                        }
534                        mRemoteDevice = mConnSocket.getRemoteDevice();
535                    }
536                    if (mRemoteDevice == null) {
537                        Log.i(TAG, "getRemoteDevice() = null");
538                        break;
539                    }
540                    sRemoteDeviceName = mRemoteDevice.getName();
541                    // In case getRemoteName failed and return null
542                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
543                        sRemoteDeviceName = getString(R.string.defaultname);
544                    }
545                    int permission = mRemoteDevice.getPhonebookAccessPermission();
546                    if (VERBOSE) Log.v(TAG, "getPhonebookAccessPermission() = " + permission);
547
548                    if (permission == BluetoothDevice.ACCESS_ALLOWED) {
549                        try {
550                            if (VERBOSE) {
551                                Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
552                                        + " automatically as already allowed device");
553                            }
554                            startObexServerSession();
555                        } catch (IOException ex) {
556                            Log.e(TAG, "Caught exception starting obex server session"
557                                    + ex.toString());
558                        }
559                    } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
560                        if (VERBOSE) {
561                            Log.v(TAG, "incoming connection rejected from: " + sRemoteDeviceName
562                                    + " automatically as already rejected device");
563                        }
564                        stopObexServerSession();
565                    } else {  // permission == BluetoothDevice.ACCESS_UNKNOWN
566                        // Send an Intent to Settings app to ask user preference.
567                        Intent intent =
568                                new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
569                        intent.setPackage(getString(R.string.pairing_ui_package));
570                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
571                                        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
572                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
573                        intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
574                        intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, getName());
575
576                        mIsWaitingAuthorization = true;
577                        sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
578
579                        if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
580                                + sRemoteDeviceName);
581
582                        // In case car kit time out and try to use HFP for
583                        // phonebook
584                        // access, while UI still there waiting for user to
585                        // confirm
586                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
587                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
588                        // We will continue the process when we receive
589                        // BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app.
590                    }
591                    stopped = true; // job done ,close this thread;
592                } catch (IOException ex) {
593                    stopped=true;
594                    /*
595                    if (stopped) {
596                        break;
597                    }
598                    */
599                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
600                }
601            }
602        }
603
604        void shutdown() {
605            stopped = true;
606            interrupt();
607        }
608    }
609
610    private final Handler mSessionStatusHandler = new Handler() {
611        @Override
612        public void handleMessage(Message msg) {
613            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
614
615            switch (msg.what) {
616                case START_LISTENER:
617                    if (mAdapter.isEnabled()) {
618                        startRfcommSocketListener();
619                    }
620                    break;
621                case USER_TIMEOUT:
622                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
623                    intent.setPackage(getString(R.string.pairing_ui_package));
624                    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
625                    intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
626                                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
627                    sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
628                    mIsWaitingAuthorization = false;
629                    stopObexServerSession();
630                    break;
631                case AUTH_TIMEOUT:
632                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
633                    sendBroadcast(i);
634                    removePbapNotification(NOTIFICATION_ID_AUTH);
635                    notifyAuthCancelled();
636                    break;
637                case MSG_SERVERSESSION_CLOSE:
638                    stopObexServerSession();
639                    break;
640                case MSG_SESSION_ESTABLISHED:
641                    break;
642                case MSG_SESSION_DISCONNECTED:
643                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
644                    break;
645                case MSG_OBEX_AUTH_CHALL:
646                    createPbapNotification(AUTH_CHALL_ACTION);
647                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
648                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
649                    break;
650                case MSG_ACQUIRE_WAKE_LOCK:
651                    if (mWakeLock == null) {
652                        PowerManager pm = (PowerManager)getSystemService(
653                                          Context.POWER_SERVICE);
654                        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
655                                    "StartingObexPbapTransaction");
656                        mWakeLock.setReferenceCounted(false);
657                        mWakeLock.acquire();
658                        Log.w(TAG, "Acquire Wake Lock");
659                    }
660                    mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
661                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
662                      .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
663                    break;
664                case MSG_RELEASE_WAKE_LOCK:
665                    if (mWakeLock != null) {
666                        mWakeLock.release();
667                        mWakeLock = null;
668                        Log.w(TAG, "Release Wake Lock");
669                    }
670                    break;
671                default:
672                    break;
673            }
674        }
675    };
676
677    private void setState(int state) {
678        setState(state, BluetoothPbap.RESULT_SUCCESS);
679    }
680
681    private synchronized void setState(int state, int result) {
682        if (state != mState) {
683            if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
684                    + result);
685            int prevState = mState;
686            mState = state;
687            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
688            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
689            intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
690            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
691            sendBroadcast(intent, BLUETOOTH_PERM);
692        }
693    }
694
695    protected int getState() {
696        return mState;
697    }
698
699    protected BluetoothDevice getRemoteDevice() {
700        return mRemoteDevice;
701    }
702
703    private void createPbapNotification(String action) {
704
705        NotificationManager nm = (NotificationManager)
706            getSystemService(Context.NOTIFICATION_SERVICE);
707
708        // Create an intent triggered by clicking on the status icon.
709        Intent clickIntent = new Intent();
710        clickIntent.setClass(this, BluetoothPbapActivity.class);
711        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
712        clickIntent.setAction(action);
713
714        // Create an intent triggered by clicking on the
715        // "Clear All Notifications" button
716        Intent deleteIntent = new Intent();
717        deleteIntent.setClass(this, BluetoothPbapService.class);
718        deleteIntent.setAction(AUTH_CANCELLED_ACTION);
719
720        String name = getRemoteDeviceName();
721
722        if (action.equals(AUTH_CHALL_ACTION)) {
723            Notification notification =
724                    new Notification.Builder(this)
725                            .setWhen(System.currentTimeMillis())
726                            .setContentTitle(getString(R.string.auth_notif_title))
727                            .setContentText(getString(R.string.auth_notif_message, name))
728                            .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
729                            .setTicker(getString(R.string.auth_notif_ticker))
730                            .setColor(getResources().getColor(
731                                    com.android.internal.R.color.system_notification_accent_color,
732                                    this.getTheme()))
733                            .setFlag(Notification.FLAG_AUTO_CANCEL, true)
734                            .setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
735                            .setDefaults(Notification.DEFAULT_SOUND)
736                            .setContentIntent(PendingIntent.getActivity(this, 0, clickIntent, 0))
737                            .setDeleteIntent(PendingIntent.getBroadcast(this, 0, deleteIntent, 0))
738                            .build();
739            nm.notify(NOTIFICATION_ID_AUTH, notification);
740        }
741    }
742
743    private void removePbapNotification(int id) {
744        NotificationManager nm = (NotificationManager)
745            getSystemService(Context.NOTIFICATION_SERVICE);
746        nm.cancel(id);
747    }
748
749    public static String getLocalPhoneNum() {
750        return sLocalPhoneNum;
751    }
752
753    public static String getLocalPhoneName() {
754        return sLocalPhoneName;
755    }
756
757    public static String getRemoteDeviceName() {
758        return sRemoteDeviceName;
759    }
760
761    @Override
762    protected IProfileServiceBinder initBinder() {
763        return new PbapBinder(this);
764    }
765
766    @Override
767    protected boolean start() {
768        Log.v(TAG, "start()");
769        IntentFilter filter = new IntentFilter();
770        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
771        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
772        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
773        filter.addAction(AUTH_RESPONSE_ACTION);
774        filter.addAction(AUTH_CANCELLED_ACTION);
775
776        try {
777            registerReceiver(mPbapReceiver, filter);
778            mIsRegistered = true;
779        } catch (Exception e) {
780            Log.w(TAG, "Unable to register pbap receiver", e);
781        }
782        mInterrupted = false;
783        BluetoothPbapConfig.init(this);
784        mAdapter = BluetoothAdapter.getDefaultAdapter();
785        return true;
786    }
787
788    @Override
789    protected boolean stop() {
790        Log.v(TAG, "stop()");
791        if (!mIsRegistered) {
792            Log.i(TAG, "Avoid unregister when receiver it is not registered");
793            return true;
794        }
795        try {
796            mIsRegistered = false;
797            unregisterReceiver(mPbapReceiver);
798        } catch (Exception e) {
799            Log.w(TAG, "Unable to unregister pbap receiver", e);
800        }
801        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
802        closeService();
803        if (mSessionStatusHandler != null) {
804            mSessionStatusHandler.removeCallbacksAndMessages(null);
805        }
806        return true;
807    }
808
809    protected void disconnect() {
810        synchronized (this) {
811            if (mState == BluetoothPbap.STATE_CONNECTED) {
812                if (mServerSession != null) {
813                    mServerSession.close();
814                    mServerSession = null;
815                }
816
817                closeConnectionSocket();
818
819                setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
820            }
821        }
822    }
823
824    // Has to be a static class or a memory leak can occur.
825    private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder {
826        private BluetoothPbapService mService;
827
828        private BluetoothPbapService getService(String perm) {
829            if (!Utils.checkCaller()) {
830                Log.w(TAG, "not allowed for non-active user");
831                return null;
832            }
833            if (mService != null && mService.isAvailable()) {
834                mService.enforceCallingOrSelfPermission(perm, "Need " + perm + " permission");
835                return mService;
836            }
837            return null;
838        }
839
840        PbapBinder(BluetoothPbapService service) {
841            Log.v(TAG, "PbapBinder()");
842            mService = service;
843        }
844
845        public boolean cleanup() {
846            mService = null;
847            return true;
848        }
849
850        public int getState() {
851            if (DEBUG) Log.d(TAG, "getState = " + mService.getState());
852            BluetoothPbapService service = getService(BLUETOOTH_PERM);
853            if (service == null) return BluetoothPbap.STATE_DISCONNECTED;
854
855            return service.getState();
856        }
857
858        public BluetoothDevice getClient() {
859            if (DEBUG) Log.d(TAG, "getClient = " + mService.getRemoteDevice());
860            BluetoothPbapService service = getService(BLUETOOTH_PERM);
861            if (service == null) return null;
862            return service.getRemoteDevice();
863        }
864
865        public boolean isConnected(BluetoothDevice device) {
866            if (DEBUG) Log.d(TAG, "isConnected " + device);
867            BluetoothPbapService service = getService(BLUETOOTH_PERM);
868            if (service == null) return false;
869            return service.getState() == BluetoothPbap.STATE_CONNECTED
870                    && service.getRemoteDevice().equals(device);
871        }
872
873        public boolean connect(BluetoothDevice device) {
874            BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
875            return false;
876        }
877
878        public void disconnect() {
879            if (DEBUG) Log.d(TAG, "disconnect");
880            BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
881            if (service == null) return;
882            service.disconnect();
883        }
884    }
885}
886