• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
33 package com.android.bluetooth.pbap;
34 
35 import com.android.bluetooth.R;
36 
37 import android.app.Notification;
38 import android.app.NotificationManager;
39 import android.app.PendingIntent;
40 import android.app.Service;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.content.res.Resources;
44 import android.bluetooth.BluetoothAdapter;
45 import android.bluetooth.BluetoothDevice;
46 import android.bluetooth.BluetoothPbap;
47 import android.bluetooth.BluetoothSocket;
48 import android.bluetooth.BluetoothServerSocket;
49 import android.bluetooth.IBluetoothPbap;
50 import android.os.Handler;
51 import android.os.IBinder;
52 import android.os.Message;
53 import android.os.PowerManager;
54 import android.provider.ContactsContract.RawContacts;
55 import android.telephony.TelephonyManager;
56 import android.text.TextUtils;
57 import android.util.Log;
58 
59 import java.io.IOException;
60 import java.util.ArrayList;
61 
62 import javax.obex.ServerSession;
63 
64 public class BluetoothPbapService extends Service {
65     private static final String TAG = "BluetoothPbapService";
66 
67     /**
68      * To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
69      * restart com.android.bluetooth process. only enable DEBUG log:
70      * "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and
71      * DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE"
72      */
73 
74     public static final boolean DEBUG = false;
75 
76     public static final boolean VERBOSE = false;
77 
78     /**
79      * Intent indicating incoming 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 
BluetoothPbapService()197     public BluetoothPbapService() {
198         mState = BluetoothPbap.STATE_DISCONNECTED;
199     }
200 
201     @Override
onCreate()202     public void onCreate() {
203         super.onCreate();
204         if (VERBOSE) Log.v(TAG, "Pbap Service onCreate");
205 
206         mInterrupted = false;
207         mAdapter = BluetoothAdapter.getDefaultAdapter();
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
onStartCommand(Intent intent, int flags, int startId)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
parseIntent(final Intent intent)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
onDestroy()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
onBind(Intent intent)300     public IBinder onBind(Intent intent) {
301         if (VERBOSE) Log.v(TAG, "Pbap Service onBind");
302         return mBinder;
303     }
304 
startRfcommSocketListener()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 
initSocket()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 
closeSocket(boolean server, boolean accept)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 
closeService()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 
startObexServerSession()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             sLocalPhoneName = tm.getLine1AlphaTag();
425             if (TextUtils.isEmpty(sLocalPhoneName)) {
426                 sLocalPhoneName = this.getString(R.string.localPhoneName);
427             }
428         }
429 
430         mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
431         synchronized (this) {
432             mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
433             mAuth.setChallenged(false);
434             mAuth.setCancelled(false);
435         }
436         BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket);
437         mServerSession = new ServerSession(transport, mPbapServer, mAuth);
438         setState(BluetoothPbap.STATE_CONNECTED);
439         if (VERBOSE) {
440             Log.v(TAG, "startObexServerSession() success!");
441         }
442     }
443 
stopObexServerSession()444     private void stopObexServerSession() {
445         if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
446 
447         // Release the wake lock if obex transaction is over
448         if (mWakeLock != null) {
449             mWakeLock.release();
450             mWakeLock = null;
451         }
452 
453         if (mServerSession != null) {
454             mServerSession.close();
455             mServerSession = null;
456         }
457 
458         mAcceptThread = null;
459 
460         try {
461             closeSocket(false, true);
462             mConnSocket = null;
463         } catch (IOException e) {
464             Log.e(TAG, "closeSocket error: " + e.toString());
465         }
466         // Last obex transaction is finished, we start to listen for incoming
467         // connection again
468         if (mAdapter.isEnabled()) {
469             startRfcommSocketListener();
470         }
471         setState(BluetoothPbap.STATE_DISCONNECTED);
472     }
473 
notifyAuthKeyInput(final String key)474     private void notifyAuthKeyInput(final String key) {
475         synchronized (mAuth) {
476             if (key != null) {
477                 mAuth.setSessionKey(key);
478             }
479             mAuth.setChallenged(true);
480             mAuth.notify();
481         }
482     }
483 
notifyAuthCancelled()484     private void notifyAuthCancelled() {
485         synchronized (mAuth) {
486             mAuth.setCancelled(true);
487             mAuth.notify();
488         }
489     }
490 
491     /**
492      * A thread that runs in the background waiting for remote rfcomm
493      * connect.Once a remote socket connected, this thread shall be
494      * shutdown.When the remote disconnect,this thread shall run again waiting
495      * for next request.
496      */
497     private class SocketAcceptThread extends Thread {
498 
499         private boolean stopped = false;
500 
501         @Override
run()502         public void run() {
503             while (!stopped) {
504                 try {
505                     mConnSocket = mServerSocket.accept();
506 
507                     mRemoteDevice = mConnSocket.getRemoteDevice();
508                     if (mRemoteDevice == null) {
509                         Log.i(TAG, "getRemoteDevice() = null");
510                         break;
511                     }
512                     sRemoteDeviceName = mRemoteDevice.getName();
513                     // In case getRemoteName failed and return null
514                     if (TextUtils.isEmpty(sRemoteDeviceName)) {
515                         sRemoteDeviceName = getString(R.string.defaultname);
516                     }
517                     boolean trust = mRemoteDevice.getTrustState();
518                     if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
519 
520                     if (trust) {
521                         try {
522                             if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
523                                 + sRemoteDeviceName + " automatically as trusted device");
524                             startObexServerSession();
525                         } catch (IOException ex) {
526                             Log.e(TAG, "catch exception starting obex server session"
527                                     + ex.toString());
528                         }
529                     } else {
530                         createPbapNotification(ACCESS_REQUEST_ACTION);
531                         if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
532                                 + sRemoteDeviceName);
533 
534                         // In case car kit time out and try to use HFP for
535                         // phonebook
536                         // access, while UI still there waiting for user to
537                         // confirm
538                         mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
539                                 .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
540                     }
541                     stopped = true; // job done ,close this thread;
542                 } catch (IOException ex) {
543                     if (stopped) {
544                         break;
545                     }
546                     if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
547                 }
548             }
549         }
550 
shutdown()551         void shutdown() {
552             stopped = true;
553             interrupt();
554         }
555     }
556 
557     private final Handler mSessionStatusHandler = new Handler() {
558         @Override
559         public void handleMessage(Message msg) {
560             if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
561 
562             CharSequence tmpTxt;
563             switch (msg.what) {
564                 case START_LISTENER:
565                     if (mAdapter.isEnabled()) {
566                         startRfcommSocketListener();
567                     } else {
568                         closeService();// release all resources
569                     }
570                     break;
571                 case USER_TIMEOUT:
572                     Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
573                     sendBroadcast(intent);
574                     removePbapNotification(NOTIFICATION_ID_ACCESS);
575                     stopObexServerSession();
576                     break;
577                 case AUTH_TIMEOUT:
578                     Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
579                     sendBroadcast(i);
580                     removePbapNotification(NOTIFICATION_ID_AUTH);
581                     notifyAuthCancelled();
582                     break;
583                 case MSG_SERVERSESSION_CLOSE:
584                     stopObexServerSession();
585                     break;
586                 case MSG_SESSION_ESTABLISHED:
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 
setState(int state)602     private void setState(int state) {
603         setState(state, BluetoothPbap.RESULT_SUCCESS);
604     }
605 
setState(int state, int result)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 
createPbapNotification(String action)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 
removePbapNotification(int id)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 
getLocalPhoneNum()674     public static String getLocalPhoneNum() {
675         return sLocalPhoneNum;
676     }
677 
getLocalPhoneName()678     public static String getLocalPhoneName() {
679         return sLocalPhoneName;
680     }
681 
getRemoteDeviceName()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                             mConnSocket = null;
733                         } catch (IOException ex) {
734                             Log.e(TAG, "Caught the error: " + ex);
735                         }
736                         setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
737                         break;
738                     default:
739                         break;
740                 }
741             }
742         }
743     };
744 }
745