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