BluetoothPbapService.java revision 2f8fe66f45bac2bc61395bd4f8de553f4c2b7c30
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 void onStart(Intent intent, int startId) {
224        if (VERBOSE) Log.v(TAG, "Pbap Service onStart");
225
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            parseIntent(intent);
234        }
235    }
236
237    // process the intent from receiver
238    private void parseIntent(final Intent intent) {
239        if (intent == null) {
240            return;
241        }
242        String action = intent.getStringExtra("action");
243        if (VERBOSE) Log.v(TAG, "action: " + action);
244
245        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
246        boolean removeTimeoutMsg = true;
247        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
248            removeTimeoutMsg = false;
249            if (state == BluetoothAdapter.STATE_OFF) {
250                // Release all resources
251                closeService();
252            }
253        } else if (action.equals(ACCESS_ALLOWED_ACTION)) {
254            if (intent.getBooleanExtra(EXTRA_ALWAYS_ALLOWED, false)) {
255                boolean result = mRemoteDevice.setTrust(true);
256                if (VERBOSE) Log.v(TAG, "setTrust() result=" + result);
257            }
258            try {
259                if (mConnSocket != null) {
260                    startObexServerSession();
261                } else {
262                    stopObexServerSession();
263                }
264            } catch (IOException ex) {
265                Log.e(TAG, "Caught the error: " + ex.toString());
266            }
267        } else if (action.equals(ACCESS_DISALLOWED_ACTION)) {
268            stopObexServerSession();
269        } else if (action.equals(AUTH_RESPONSE_ACTION)) {
270            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
271            notifyAuthKeyInput(sessionkey);
272        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
273            notifyAuthCancelled();
274        } else {
275            removeTimeoutMsg = false;
276        }
277
278        if (removeTimeoutMsg) {
279            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
280        }
281    }
282
283    @Override
284    public void onDestroy() {
285        if (VERBOSE) Log.v(TAG, "Pbap Service onDestroy");
286
287        super.onDestroy();
288        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
289        if (mWakeLock != null) {
290            mWakeLock.release();
291            mWakeLock = null;
292        }
293    }
294
295    @Override
296    public IBinder onBind(Intent intent) {
297        if (VERBOSE) Log.v(TAG, "Pbap Service onBind");
298        return mBinder;
299    }
300
301    private void startRfcommSocketListener() {
302        if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener");
303
304        if (mServerSocket == null) {
305            if (!initSocket()) {
306                closeService();
307                return;
308            }
309        }
310        if (mAcceptThread == null) {
311            mAcceptThread = new SocketAcceptThread();
312            mAcceptThread.setName("BluetoothPbapAcceptThread");
313            mAcceptThread.start();
314        }
315    }
316
317    private final boolean initSocket() {
318        if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
319
320        boolean initSocketOK = true;
321        final int CREATE_RETRY_TIME = 10;
322
323        // It's possible that create will fail in some cases. retry for 10 times
324        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
325            try {
326                // It is mandatory for PSE to support initiation of bonding and
327                // encryption.
328                mServerSocket = mAdapter.listenUsingRfcommOn(PORT_NUM);
329            } catch (IOException e) {
330                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
331                initSocketOK = false;
332            }
333            if (!initSocketOK) {
334                synchronized (this) {
335                    try {
336                        if (VERBOSE) Log.v(TAG, "wait 3 seconds");
337                        Thread.sleep(3000);
338                    } catch (InterruptedException e) {
339                        Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
340                        mInterrupted = true;
341                    }
342                }
343            } else {
344                break;
345            }
346        }
347
348        if (initSocketOK) {
349            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket on channel " + PORT_NUM);
350
351        } else {
352            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
353        }
354        return initSocketOK;
355    }
356
357    private final void closeSocket(boolean server, boolean accept) throws IOException {
358        if (server == true) {
359            // Stop the possible trying to init serverSocket
360            mInterrupted = true;
361
362            if (mServerSocket != null) {
363                mServerSocket.close();
364            }
365            mServerSocket = null;
366        }
367
368        if (accept == true) {
369            if (mConnSocket != null) {
370                mConnSocket.close();
371            }
372            mConnSocket = null;
373        }
374    }
375
376    private final void closeService() {
377        if (VERBOSE) Log.v(TAG, "Pbap Service closeService");
378
379        try {
380            closeSocket(true, true);
381        } catch (IOException ex) {
382            Log.e(TAG, "CloseSocket error: " + ex);
383        }
384
385        if (mAcceptThread != null) {
386            try {
387                mAcceptThread.shutdown();
388                mAcceptThread.join();
389                mAcceptThread = null;
390            } catch (InterruptedException ex) {
391                Log.w(TAG, "mAcceptThread close error" + ex);
392            }
393        }
394        if (mServerSession != null) {
395            mServerSession.close();
396            mServerSession = null;
397        }
398
399        mHasStarted = false;
400        if (stopSelfResult(mStartId)) {
401            if (VERBOSE) Log.v(TAG, "successfully stopped pbap service");
402        }
403    }
404
405    private final void startObexServerSession() throws IOException {
406        if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
407
408        // acquire the wakeLock before start Obex transaction thread
409        if (mWakeLock == null) {
410            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
411            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
412                    "StartingObexPbapTransaction");
413            mWakeLock.setReferenceCounted(false);
414            mWakeLock.acquire();
415        }
416        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
417        if (tm != null) {
418            sLocalPhoneNum = tm.getLine1Number();
419            if (TextUtils.isEmpty(sLocalPhoneNum)) {
420                // Default number (000000) should be ok
421                sLocalPhoneNum = this.getString(R.string.defaultnumber);
422            }
423            sLocalPhoneName = tm.getLine1AlphaTag();
424            if (TextUtils.isEmpty(sLocalPhoneName)) {
425                sLocalPhoneName = this.getString(R.string.unknownName);
426            }
427        }
428
429        mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
430        synchronized (this) {
431            mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
432            mAuth.setChallenged(false);
433            mAuth.setCancelled(false);
434        }
435        BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket);
436        mServerSession = new ServerSession(transport, mPbapServer, mAuth);
437        setState(BluetoothPbap.STATE_CONNECTED);
438        if (VERBOSE) {
439            Log.v(TAG, "startObexServerSession() success!");
440        }
441    }
442
443    private void stopObexServerSession() {
444        if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
445
446        // Release the wake lock if obex transaction is over
447        if (mWakeLock != null) {
448            mWakeLock.release();
449            mWakeLock = null;
450        }
451
452        if (mServerSession != null) {
453            mServerSession.close();
454            mServerSession = null;
455        }
456
457        mAcceptThread = null;
458
459        try {
460            closeSocket(false, true);
461        } catch (IOException e) {
462            Log.e(TAG, "closeSocket error: " + e.toString());
463        }
464        // Last obex transaction is finished, we start to listen for incoming
465        // connection again
466        if (mAdapter.isEnabled()) {
467            startRfcommSocketListener();
468        }
469        setState(BluetoothPbap.STATE_DISCONNECTED);
470    }
471
472    private void notifyAuthKeyInput(final String key) {
473        synchronized (mAuth) {
474            if (key != null) {
475                mAuth.setSessionKey(key);
476            }
477            mAuth.setChallenged(true);
478            mAuth.notify();
479        }
480    }
481
482    private void notifyAuthCancelled() {
483        synchronized (mAuth) {
484            mAuth.setCancelled(true);
485            mAuth.notify();
486        }
487    }
488
489    /**
490     * A thread that runs in the background waiting for remote rfcomm
491     * connect.Once a remote socket connected, this thread shall be
492     * shutdown.When the remote disconnect,this thread shall run again waiting
493     * for next request.
494     */
495    private class SocketAcceptThread extends Thread {
496
497        private boolean stopped = false;
498
499        @Override
500        public void run() {
501            while (!stopped) {
502                try {
503                    mConnSocket = mServerSocket.accept();
504
505                    mRemoteDevice = mConnSocket.getRemoteDevice();
506                    if (mRemoteDevice != null) {
507                        sRemoteDeviceName = mRemoteDevice.getName();
508                        // In case getRemoteName failed and return null
509                        if (sRemoteDeviceName == null) {
510                            sRemoteDeviceName = getString(R.string.defaultname);
511                        }
512                    }
513                    boolean trust = mRemoteDevice.getTrustState();
514                    if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
515
516                    if (trust) {
517                        try {
518                            if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
519                                + sRemoteDeviceName + " automatically as trusted device");
520                            startObexServerSession();
521                        } catch (IOException ex) {
522                            Log.e(TAG, "catch exception starting obex server session"
523                                    + ex.toString());
524                        }
525                    } else {
526                        createPbapNotification(ACCESS_REQUEST_ACTION);
527                        if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
528                                + sRemoteDeviceName);
529
530                        // In case car kit time out and try to use HFP for
531                        // phonebook
532                        // access, while UI still there waiting for user to
533                        // confirm
534                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
535                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
536                    }
537                    stopped = true; // job done ,close this thread;
538                } catch (IOException ex) {
539                    if (stopped) {
540                        break;
541                    }
542                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
543                }
544            }
545        }
546
547        void shutdown() {
548            stopped = true;
549            interrupt();
550        }
551    }
552
553    private final Handler mSessionStatusHandler = new Handler() {
554        @Override
555        public void handleMessage(Message msg) {
556            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
557
558            CharSequence tmpTxt;
559            switch (msg.what) {
560                case START_LISTENER:
561                    if (mAdapter.isEnabled()) {
562                        startRfcommSocketListener();
563                    } else {
564                        closeService();// release all resources
565                    }
566                    break;
567                case USER_TIMEOUT:
568                    Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
569                    sendBroadcast(intent);
570                    removePbapNotification(NOTIFICATION_ID_ACCESS);
571                    stopObexServerSession();
572                    break;
573                case AUTH_TIMEOUT:
574                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
575                    sendBroadcast(i);
576                    removePbapNotification(NOTIFICATION_ID_AUTH);
577                    notifyAuthCancelled();
578                    break;
579                case MSG_SERVERSESSION_CLOSE:
580                    stopObexServerSession();
581                    tmpTxt = getString(R.string.toast_disconnected, sRemoteDeviceName);
582                    Toast.makeText(BluetoothPbapService.this, tmpTxt, Toast.LENGTH_SHORT).show();
583                    break;
584                case MSG_SESSION_ESTABLISHED:
585                    tmpTxt = getString(R.string.toast_connected, sRemoteDeviceName);
586                    Toast.makeText(BluetoothPbapService.this, tmpTxt, Toast.LENGTH_SHORT).show();
587                    break;
588                case MSG_SESSION_DISCONNECTED:
589                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
590                    break;
591                case MSG_OBEX_AUTH_CHALL:
592                    createPbapNotification(AUTH_CHALL_ACTION);
593                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
594                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
595                    break;
596                default:
597                    break;
598            }
599        }
600    };
601
602    private void setState(int state) {
603        setState(state, BluetoothPbap.RESULT_SUCCESS);
604    }
605
606    private synchronized void setState(int state, int result) {
607        if (state != mState) {
608            if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
609                    + result);
610            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
611            intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, mState);
612            mState = state;
613            intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
614            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
615            sendBroadcast(intent, BLUETOOTH_PERM);
616        }
617    }
618
619    private void createPbapNotification(String action) {
620        Context context = getApplicationContext();
621
622        NotificationManager nm = (NotificationManager)context
623                .getSystemService(Context.NOTIFICATION_SERVICE);
624
625        // Create an intent triggered by clicking on the status icon.
626        Intent clickIntent = new Intent();
627        clickIntent.setClass(context, BluetoothPbapActivity.class);
628        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
629        clickIntent.setAction(action);
630
631        // Create an intent triggered by clicking on the
632        // "Clear All Notifications" button
633        Intent deleteIntent = new Intent();
634        deleteIntent.setClass(context, BluetoothPbapReceiver.class);
635
636        Notification notification = null;
637        String name = getRemoteDeviceName();
638        if (action.equals(ACCESS_REQUEST_ACTION)) {
639            deleteIntent.setAction(ACCESS_DISALLOWED_ACTION);
640            notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, context
641                    .getString(R.string.pbap_notif_ticker), System.currentTimeMillis());
642            notification.setLatestEventInfo(context, context.getString(R.string.pbap_notif_title),
643                    context.getString(R.string.pbap_notif_message, name), PendingIntent
644                            .getActivity(context, 0, clickIntent, 0));
645
646            notification.flags |= Notification.FLAG_AUTO_CANCEL;
647            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
648            notification.defaults = Notification.DEFAULT_SOUND;
649            notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
650            nm.notify(NOTIFICATION_ID_ACCESS, notification);
651        } else if (action.equals(AUTH_CHALL_ACTION)) {
652            deleteIntent.setAction(AUTH_CANCELLED_ACTION);
653            notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, context
654                    .getString(R.string.auth_notif_ticker), System.currentTimeMillis());
655            notification.setLatestEventInfo(context, context.getString(R.string.auth_notif_title),
656                    context.getString(R.string.auth_notif_message, name), PendingIntent
657                            .getActivity(context, 0, clickIntent, 0));
658
659            notification.flags |= Notification.FLAG_AUTO_CANCEL;
660            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
661            notification.defaults = Notification.DEFAULT_SOUND;
662            notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
663            nm.notify(NOTIFICATION_ID_AUTH, notification);
664        }
665    }
666
667    private void removePbapNotification(int id) {
668        Context context = getApplicationContext();
669        NotificationManager nm = (NotificationManager)context
670                .getSystemService(Context.NOTIFICATION_SERVICE);
671        nm.cancel(id);
672    }
673
674    public static String getLocalPhoneNum() {
675        return sLocalPhoneNum;
676    }
677
678    public static String getLocalPhoneName() {
679        return sLocalPhoneName;
680    }
681
682    public static String getRemoteDeviceName() {
683        return sRemoteDeviceName;
684    }
685
686    /**
687     * Handlers for incoming service calls
688     */
689    private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() {
690        public int getState() {
691            if (DEBUG) Log.d(TAG, "getState " + mState);
692
693            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
694            return mState;
695        }
696
697        public BluetoothDevice getClient() {
698            if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice);
699
700            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
701            if (mState == BluetoothPbap.STATE_DISCONNECTED) {
702                return null;
703            }
704            return mRemoteDevice;
705        }
706
707        public boolean isConnected(BluetoothDevice device) {
708            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
709            return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device);
710        }
711
712        public boolean connect(BluetoothDevice device) {
713            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
714                    "Need BLUETOOTH_ADMIN permission");
715            return false;
716        }
717
718        public void disconnect() {
719            if (DEBUG) Log.d(TAG, "disconnect");
720
721            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
722                    "Need BLUETOOTH_ADMIN permission");
723            synchronized (BluetoothPbapService.this) {
724                switch (mState) {
725                    case BluetoothPbap.STATE_CONNECTED:
726                        if (mServerSession != null) {
727                            mServerSession.close();
728                            mServerSession = null;
729                        }
730                        try {
731                            closeSocket(false, true);
732                        } catch (IOException ex) {
733                            Log.e(TAG, "Caught the error: " + ex);
734                        }
735                        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
736                        break;
737                    default:
738                        break;
739                }
740            }
741        }
742    };
743}
744