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