Home | History | Annotate | Download | only in pbap
      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 
    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.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
    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             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 
    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 
    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 
    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
    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 
    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 
    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 
    621         NotificationManager nm = (NotificationManager)
    622             getSystemService(Context.NOTIFICATION_SERVICE);
    623 
    624         // Create an intent triggered by clicking on the status icon.
    625         Intent clickIntent = new Intent();
    626         clickIntent.setClass(this, BluetoothPbapActivity.class);
    627         clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    628         clickIntent.setAction(action);
    629 
    630         // Create an intent triggered by clicking on the
    631         // "Clear All Notifications" button
    632         Intent deleteIntent = new Intent();
    633         deleteIntent.setClass(this, BluetoothPbapReceiver.class);
    634 
    635         Notification notification = null;
    636         String name = getRemoteDeviceName();
    637 
    638         if (action.equals(ACCESS_REQUEST_ACTION)) {
    639             deleteIntent.setAction(ACCESS_DISALLOWED_ACTION);
    640             notification = new Notification(android.R.drawable.stat_sys_data_bluetooth,
    641                 getString(R.string.pbap_notif_ticker), System.currentTimeMillis());
    642             notification.setLatestEventInfo(this, getString(R.string.pbap_notif_title),
    643                     getString(R.string.pbap_notif_message, name), PendingIntent
    644                             .getActivity(this, 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(this, 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,
    654                 getString(R.string.auth_notif_ticker), System.currentTimeMillis());
    655             notification.setLatestEventInfo(this, getString(R.string.auth_notif_title),
    656                     getString(R.string.auth_notif_message, name), PendingIntent
    657                             .getActivity(this, 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(this, 0, deleteIntent, 0);
    663             nm.notify(NOTIFICATION_ID_AUTH, notification);
    664         }
    665     }
    666 
    667     private void removePbapNotification(int id) {
    668         NotificationManager nm = (NotificationManager)
    669             getSystemService(Context.NOTIFICATION_SERVICE);
    670         nm.cancel(id);
    671     }
    672 
    673     public static String getLocalPhoneNum() {
    674         return sLocalPhoneNum;
    675     }
    676 
    677     public static String getLocalPhoneName() {
    678         return sLocalPhoneName;
    679     }
    680 
    681     public static String getRemoteDeviceName() {
    682         return sRemoteDeviceName;
    683     }
    684 
    685     /**
    686      * Handlers for incoming service calls
    687      */
    688     private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() {
    689         public int getState() {
    690             if (DEBUG) Log.d(TAG, "getState " + mState);
    691 
    692             enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    693             return mState;
    694         }
    695 
    696         public BluetoothDevice getClient() {
    697             if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice);
    698 
    699             enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    700             if (mState == BluetoothPbap.STATE_DISCONNECTED) {
    701                 return null;
    702             }
    703             return mRemoteDevice;
    704         }
    705 
    706         public boolean isConnected(BluetoothDevice device) {
    707             enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    708             return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device);
    709         }
    710 
    711         public boolean connect(BluetoothDevice device) {
    712             enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    713                     "Need BLUETOOTH_ADMIN permission");
    714             return false;
    715         }
    716 
    717         public void disconnect() {
    718             if (DEBUG) Log.d(TAG, "disconnect");
    719 
    720             enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
    721                     "Need BLUETOOTH_ADMIN permission");
    722             synchronized (BluetoothPbapService.this) {
    723                 switch (mState) {
    724                     case BluetoothPbap.STATE_CONNECTED:
    725                         if (mServerSession != null) {
    726                             mServerSession.close();
    727                             mServerSession = null;
    728                         }
    729                         try {
    730                             closeSocket(false, true);
    731                             mConnSocket = null;
    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