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