BluetoothPbapService.java revision 8222c7902a2281929fd31e840f6012038e6fa44a
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            mServerSocket = null;
371        }
372
373        if (accept == true) {
374            if (mConnSocket != null) {
375                mConnSocket.close();
376            }
377            mConnSocket = null;
378        }
379    }
380
381    private final void closeService() {
382        if (VERBOSE) Log.v(TAG, "Pbap Service closeService");
383
384        if (mAcceptThread != null) {
385            try {
386                mAcceptThread.shutdown();
387                mAcceptThread.join();
388                mAcceptThread = null;
389            } catch (InterruptedException ex) {
390                Log.w(TAG, "mAcceptThread close error" + ex);
391            }
392        }
393
394        try {
395            closeSocket(true, true);
396        } catch (IOException ex) {
397            Log.e(TAG, "CloseSocket error: " + ex);
398        }
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        } catch (IOException e) {
468            Log.e(TAG, "closeSocket error: " + e.toString());
469        }
470        // Last obex transaction is finished, we start to listen for incoming
471        // connection again
472        if (mAdapter.isEnabled()) {
473            startRfcommSocketListener();
474        }
475        setState(BluetoothPbap.STATE_DISCONNECTED);
476    }
477
478    private void notifyAuthKeyInput(final String key) {
479        synchronized (mAuth) {
480            if (key != null) {
481                mAuth.setSessionKey(key);
482            }
483            mAuth.setChallenged(true);
484            mAuth.notify();
485        }
486    }
487
488    private void notifyAuthCancelled() {
489        synchronized (mAuth) {
490            mAuth.setCancelled(true);
491            mAuth.notify();
492        }
493    }
494
495    /**
496     * A thread that runs in the background waiting for remote rfcomm
497     * connect.Once a remote socket connected, this thread shall be
498     * shutdown.When the remote disconnect,this thread shall run again waiting
499     * for next request.
500     */
501    private class SocketAcceptThread extends Thread {
502
503        private boolean stopped = false;
504
505        @Override
506        public void run() {
507            while (!stopped) {
508                try {
509                    mConnSocket = mServerSocket.accept();
510
511                    mRemoteDevice = mConnSocket.getRemoteDevice();
512                    if (mRemoteDevice == null) {
513                        Log.i(TAG, "getRemoteDevice() = null");
514                        break;
515                    }
516                    sRemoteDeviceName = mRemoteDevice.getName();
517                    // In case getRemoteName failed and return null
518                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
519                        sRemoteDeviceName = getString(R.string.defaultname);
520                    }
521                    boolean trust = mRemoteDevice.getTrustState();
522                    if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
523
524                    if (trust) {
525                        try {
526                            if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
527                                + sRemoteDeviceName + " automatically as trusted device");
528                            startObexServerSession();
529                        } catch (IOException ex) {
530                            Log.e(TAG, "catch exception starting obex server session"
531                                    + ex.toString());
532                        }
533                    } else {
534                        createPbapNotification(ACCESS_REQUEST_ACTION);
535                        if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
536                                + sRemoteDeviceName);
537
538                        // In case car kit time out and try to use HFP for
539                        // phonebook
540                        // access, while UI still there waiting for user to
541                        // confirm
542                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
543                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
544                    }
545                    stopped = true; // job done ,close this thread;
546                } catch (IOException ex) {
547                    if (stopped) {
548                        break;
549                    }
550                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
551                }
552            }
553        }
554
555        void shutdown() {
556            stopped = true;
557            interrupt();
558        }
559    }
560
561    private final Handler mSessionStatusHandler = new Handler() {
562        @Override
563        public void handleMessage(Message msg) {
564            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
565
566            CharSequence tmpTxt;
567            switch (msg.what) {
568                case START_LISTENER:
569                    if (mAdapter.isEnabled()) {
570                        startRfcommSocketListener();
571                    } else {
572                        closeService();// release all resources
573                    }
574                    break;
575                case USER_TIMEOUT:
576                    Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
577                    sendBroadcast(intent);
578                    removePbapNotification(NOTIFICATION_ID_ACCESS);
579                    stopObexServerSession();
580                    break;
581                case AUTH_TIMEOUT:
582                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
583                    sendBroadcast(i);
584                    removePbapNotification(NOTIFICATION_ID_AUTH);
585                    notifyAuthCancelled();
586                    break;
587                case MSG_SERVERSESSION_CLOSE:
588                    stopObexServerSession();
589                    tmpTxt = getString(R.string.toast_disconnected, sRemoteDeviceName);
590                    Toast.makeText(BluetoothPbapService.this, tmpTxt, Toast.LENGTH_SHORT).show();
591                    break;
592                case MSG_SESSION_ESTABLISHED:
593                    tmpTxt = getString(R.string.toast_connected, sRemoteDeviceName);
594                    Toast.makeText(BluetoothPbapService.this, tmpTxt, Toast.LENGTH_SHORT).show();
595                    break;
596                case MSG_SESSION_DISCONNECTED:
597                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
598                    break;
599                case MSG_OBEX_AUTH_CHALL:
600                    createPbapNotification(AUTH_CHALL_ACTION);
601                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
602                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
603                    break;
604                default:
605                    break;
606            }
607        }
608    };
609
610    private void setState(int state) {
611        setState(state, BluetoothPbap.RESULT_SUCCESS);
612    }
613
614    private synchronized void setState(int state, int result) {
615        if (state != mState) {
616            if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
617                    + result);
618            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
619            intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, mState);
620            mState = state;
621            intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
622            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
623            sendBroadcast(intent, BLUETOOTH_PERM);
624        }
625    }
626
627    private void createPbapNotification(String action) {
628        Context context = getApplicationContext();
629
630        NotificationManager nm = (NotificationManager)context
631                .getSystemService(Context.NOTIFICATION_SERVICE);
632
633        // Create an intent triggered by clicking on the status icon.
634        Intent clickIntent = new Intent();
635        clickIntent.setClass(context, BluetoothPbapActivity.class);
636        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
637        clickIntent.setAction(action);
638
639        // Create an intent triggered by clicking on the
640        // "Clear All Notifications" button
641        Intent deleteIntent = new Intent();
642        deleteIntent.setClass(context, BluetoothPbapReceiver.class);
643
644        Notification notification = null;
645        String name = getRemoteDeviceName();
646        if (action.equals(ACCESS_REQUEST_ACTION)) {
647            deleteIntent.setAction(ACCESS_DISALLOWED_ACTION);
648            notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, context
649                    .getString(R.string.pbap_notif_ticker), System.currentTimeMillis());
650            notification.setLatestEventInfo(context, context.getString(R.string.pbap_notif_title),
651                    context.getString(R.string.pbap_notif_message, name), PendingIntent
652                            .getActivity(context, 0, clickIntent, 0));
653
654            notification.flags |= Notification.FLAG_AUTO_CANCEL;
655            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
656            notification.defaults = Notification.DEFAULT_SOUND;
657            notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
658            nm.notify(NOTIFICATION_ID_ACCESS, notification);
659        } else if (action.equals(AUTH_CHALL_ACTION)) {
660            deleteIntent.setAction(AUTH_CANCELLED_ACTION);
661            notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, context
662                    .getString(R.string.auth_notif_ticker), System.currentTimeMillis());
663            notification.setLatestEventInfo(context, context.getString(R.string.auth_notif_title),
664                    context.getString(R.string.auth_notif_message, name), PendingIntent
665                            .getActivity(context, 0, clickIntent, 0));
666
667            notification.flags |= Notification.FLAG_AUTO_CANCEL;
668            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
669            notification.defaults = Notification.DEFAULT_SOUND;
670            notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
671            nm.notify(NOTIFICATION_ID_AUTH, notification);
672        }
673    }
674
675    private void removePbapNotification(int id) {
676        Context context = getApplicationContext();
677        NotificationManager nm = (NotificationManager)context
678                .getSystemService(Context.NOTIFICATION_SERVICE);
679        nm.cancel(id);
680    }
681
682    public static String getLocalPhoneNum() {
683        return sLocalPhoneNum;
684    }
685
686    public static String getLocalPhoneName() {
687        return sLocalPhoneName;
688    }
689
690    public static String getRemoteDeviceName() {
691        return sRemoteDeviceName;
692    }
693
694    /**
695     * Handlers for incoming service calls
696     */
697    private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() {
698        public int getState() {
699            if (DEBUG) Log.d(TAG, "getState " + mState);
700
701            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
702            return mState;
703        }
704
705        public BluetoothDevice getClient() {
706            if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice);
707
708            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
709            if (mState == BluetoothPbap.STATE_DISCONNECTED) {
710                return null;
711            }
712            return mRemoteDevice;
713        }
714
715        public boolean isConnected(BluetoothDevice device) {
716            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
717            return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device);
718        }
719
720        public boolean connect(BluetoothDevice device) {
721            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
722                    "Need BLUETOOTH_ADMIN permission");
723            return false;
724        }
725
726        public void disconnect() {
727            if (DEBUG) Log.d(TAG, "disconnect");
728
729            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
730                    "Need BLUETOOTH_ADMIN permission");
731            synchronized (BluetoothPbapService.this) {
732                switch (mState) {
733                    case BluetoothPbap.STATE_CONNECTED:
734                        if (mServerSession != null) {
735                            mServerSession.close();
736                            mServerSession = null;
737                        }
738                        try {
739                            closeSocket(false, true);
740                        } catch (IOException ex) {
741                            Log.e(TAG, "Caught the error: " + ex);
742                        }
743                        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
744                        break;
745                    default:
746                        break;
747                }
748            }
749        }
750    };
751}
752