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