BluetoothPbapService.java revision 72d2952fcde46d8c2adc68718957c2fe9f5fa3b0
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 android.app.Notification;
36import android.app.NotificationManager;
37import android.app.PendingIntent;
38import android.app.Service;
39import android.bluetooth.BluetoothAdapter;
40import android.bluetooth.BluetoothDevice;
41import android.bluetooth.BluetoothPbap;
42import android.bluetooth.BluetoothProfile;
43import android.bluetooth.BluetoothServerSocket;
44import android.bluetooth.BluetoothSocket;
45import android.bluetooth.IBluetooth;
46import android.bluetooth.IBluetoothPbap;
47import android.content.Context;
48import android.content.Intent;
49import android.os.Handler;
50import android.os.IBinder;
51import android.os.Message;
52import android.os.PowerManager;
53import android.os.RemoteException;
54import android.os.ServiceManager;
55import android.telephony.TelephonyManager;
56import android.text.TextUtils;
57import android.util.Log;
58
59import com.android.bluetooth.R;
60
61import java.io.IOException;
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 = false;
76
77    public static final boolean VERBOSE = false;
78
79    /**
80     * Intent indicating incoming obex authentication request which is from
81     * PCE(Carkit)
82     */
83    public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall";
84
85    /**
86     * Intent indicating obex session key input complete by user which is sent
87     * from BluetoothPbapActivity
88     */
89    public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse";
90
91    /**
92     * Intent indicating user canceled obex authentication session key input
93     * which is sent from BluetoothPbapActivity
94     */
95    public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled";
96
97    /**
98     * Intent indicating timeout for user confirmation, which is sent to
99     * BluetoothPbapActivity
100     */
101    public static final String USER_CONFIRM_TIMEOUT_ACTION =
102            "com.android.bluetooth.pbap.userconfirmtimeout";
103
104    /**
105     * Intent Extra name indicating session key which is sent from
106     * BluetoothPbapActivity
107     */
108    public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
109
110    public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
111
112    public static final int MSG_SERVERSESSION_CLOSE = 5000;
113
114    public static final int MSG_SESSION_ESTABLISHED = 5001;
115
116    public static final int MSG_SESSION_DISCONNECTED = 5002;
117
118    public static final int MSG_OBEX_AUTH_CHALL = 5003;
119
120    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
121
122    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
123
124    private static final int START_LISTENER = 1;
125
126    private static final int USER_TIMEOUT = 2;
127
128    private static final int AUTH_TIMEOUT = 3;
129
130    private static final int PORT_NUM = 19;
131
132    private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
133
134
135    // Ensure not conflict with Opp notification ID
136    private static final int NOTIFICATION_ID_ACCESS = -1000001;
137
138    private static final int NOTIFICATION_ID_AUTH = -1000002;
139
140    private PowerManager.WakeLock mWakeLock = null;
141
142    private BluetoothAdapter mAdapter;
143
144    private SocketAcceptThread mAcceptThread = null;
145
146    private BluetoothPbapAuthenticator mAuth = null;
147
148    private BluetoothPbapObexServer mPbapServer;
149
150    private ServerSession mServerSession = null;
151
152    private BluetoothServerSocket mServerSocket = null;
153
154    private BluetoothSocket mConnSocket = null;
155
156    private BluetoothDevice mRemoteDevice = null;
157
158    private static String sLocalPhoneNum = null;
159
160    private static String sLocalPhoneName = null;
161
162    private static String sRemoteDeviceName = null;
163
164    private boolean mHasStarted = false;
165
166    private volatile boolean mInterrupted;
167
168    private int mState;
169
170    private int mStartId = -1;
171
172    private IBluetooth mBluetoothService;
173
174    private boolean isWaitingAuthorization = false;
175
176    // package and class name to which we send intent to check phone book access permission
177    private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
178    private static final String ACCESS_AUTHORITY_CLASS =
179        "com.android.settings.bluetooth.BluetoothPermissionRequest";
180
181    public BluetoothPbapService() {
182        mState = BluetoothPbap.STATE_DISCONNECTED;
183        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
184        if (b == null) {
185            throw new RuntimeException("Bluetooth service not available");
186        }
187        mBluetoothService = IBluetooth.Stub.asInterface(b);
188    }
189
190    @Override
191    public void onCreate() {
192        super.onCreate();
193        if (VERBOSE) Log.v(TAG, "Pbap Service onCreate");
194
195        mInterrupted = false;
196        mAdapter = BluetoothAdapter.getDefaultAdapter();
197
198        if (!mHasStarted) {
199            mHasStarted = true;
200            if (VERBOSE) Log.v(TAG, "Starting PBAP service");
201
202            int state = mAdapter.getState();
203            if (state == BluetoothAdapter.STATE_ON) {
204                mSessionStatusHandler.sendMessage(mSessionStatusHandler
205                        .obtainMessage(START_LISTENER));
206            }
207        }
208    }
209
210    @Override
211    public int onStartCommand(Intent intent, int flags, int startId) {
212        if (VERBOSE) Log.v(TAG, "Pbap Service onStartCommand");
213        int retCode = super.onStartCommand(intent, flags, startId);
214        if (retCode == START_STICKY) {
215            mStartId = startId;
216            if (mAdapter == null) {
217                Log.w(TAG, "Stopping BluetoothPbapService: "
218                        + "device does not have BT or device is not ready");
219                // Release all resources
220                closeService();
221            } else {
222                // No need to handle the null intent case, because we have
223                // all restart work done in onCreate()
224                if (intent != null) {
225                    parseIntent(intent);
226                }
227            }
228        }
229        return retCode;
230    }
231
232    // process the intent from receiver
233    private void parseIntent(final Intent intent) {
234        String action = intent.getStringExtra("action");
235        if (VERBOSE) Log.v(TAG, "action: " + action);
236
237        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
238        boolean removeTimeoutMsg = true;
239        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
240            if (state == BluetoothAdapter.STATE_OFF) {
241                // Send any pending timeout now, as this service will be destroyed.
242                if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) {
243                    Intent timeoutIntent =
244                        new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
245                    timeoutIntent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
246                    sendBroadcast(timeoutIntent);
247                }
248                // Release all resources
249                closeService();
250            } else {
251                removeTimeoutMsg = false;
252            }
253        } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
254            if (!isWaitingAuthorization) {
255                // this reply is not for us
256                return;
257            }
258
259            isWaitingAuthorization = false;
260
261            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
262                                   BluetoothDevice.CONNECTION_ACCESS_NO) ==
263                BluetoothDevice.CONNECTION_ACCESS_YES) {
264
265                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
266                    boolean result = mRemoteDevice.setTrust(true);
267                    if (VERBOSE) Log.v(TAG, "setTrust() result=" + result);
268                }
269                try {
270                    if (mConnSocket != null) {
271                        startObexServerSession();
272                    } else {
273                        stopObexServerSession();
274                    }
275                } catch (IOException ex) {
276                    Log.e(TAG, "Caught the error: " + ex.toString());
277                }
278            } else {
279                stopObexServerSession();
280            }
281        } else if (action.equals(AUTH_RESPONSE_ACTION)) {
282            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
283            notifyAuthKeyInput(sessionkey);
284        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
285            notifyAuthCancelled();
286        } else {
287            removeTimeoutMsg = false;
288        }
289
290        if (removeTimeoutMsg) {
291            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
292        }
293    }
294
295    @Override
296    public void onDestroy() {
297        if (VERBOSE) Log.v(TAG, "Pbap Service onDestroy");
298
299        super.onDestroy();
300        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
301        if (mWakeLock != null) {
302            mWakeLock.release();
303            mWakeLock = null;
304        }
305        closeService();
306    }
307
308    @Override
309    public IBinder onBind(Intent intent) {
310        if (VERBOSE) Log.v(TAG, "Pbap Service onBind");
311        return mBinder;
312    }
313
314    private void startRfcommSocketListener() {
315        if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener");
316
317        if (mServerSocket == null) {
318            if (!initSocket()) {
319                closeService();
320                return;
321            }
322        }
323        if (mAcceptThread == null) {
324            mAcceptThread = new SocketAcceptThread();
325            mAcceptThread.setName("BluetoothPbapAcceptThread");
326            mAcceptThread.start();
327        }
328    }
329
330    private final boolean initSocket() {
331        if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
332
333        boolean initSocketOK = true;
334        final int CREATE_RETRY_TIME = 10;
335
336        // It's possible that create will fail in some cases. retry for 10 times
337        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
338            try {
339                // It is mandatory for PSE to support initiation of bonding and
340                // encryption.
341                mServerSocket = mAdapter.listenUsingEncryptedRfcommOn(PORT_NUM);
342            } catch (IOException e) {
343                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
344                initSocketOK = false;
345            }
346            if (!initSocketOK) {
347                synchronized (this) {
348                    try {
349                        if (VERBOSE) Log.v(TAG, "wait 3 seconds");
350                        Thread.sleep(3000);
351                    } catch (InterruptedException e) {
352                        Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
353                        mInterrupted = true;
354                    }
355                }
356            } else {
357                break;
358            }
359        }
360
361        if (initSocketOK) {
362            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket on channel " + PORT_NUM);
363
364        } else {
365            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
366        }
367        return initSocketOK;
368    }
369
370    private final void closeSocket(boolean server, boolean accept) throws IOException {
371        if (server == true) {
372            // Stop the possible trying to init serverSocket
373            mInterrupted = true;
374
375            if (mServerSocket != null) {
376                mServerSocket.close();
377            }
378        }
379
380        if (accept == true) {
381            if (mConnSocket != null) {
382                mConnSocket.close();
383            }
384        }
385    }
386
387    private final void closeService() {
388        if (VERBOSE) Log.v(TAG, "Pbap Service closeService");
389
390        try {
391            closeSocket(true, true);
392        } catch (IOException ex) {
393            Log.e(TAG, "CloseSocket error: " + ex);
394        }
395
396        if (mAcceptThread != null) {
397            try {
398                mAcceptThread.shutdown();
399                mAcceptThread.join();
400                mAcceptThread = null;
401            } catch (InterruptedException ex) {
402                Log.w(TAG, "mAcceptThread close error" + ex);
403            }
404        }
405        mServerSocket = null;
406        mConnSocket = null;
407
408        if (mServerSession != null) {
409            mServerSession.close();
410            mServerSession = null;
411        }
412
413        mHasStarted = false;
414        if (stopSelfResult(mStartId)) {
415            if (VERBOSE) Log.v(TAG, "successfully stopped pbap service");
416        }
417    }
418
419    private final void startObexServerSession() throws IOException {
420        if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
421
422        // acquire the wakeLock before start Obex transaction thread
423        if (mWakeLock == null) {
424            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
425            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
426                    "StartingObexPbapTransaction");
427            mWakeLock.setReferenceCounted(false);
428            mWakeLock.acquire();
429        }
430        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
431        if (tm != null) {
432            sLocalPhoneNum = tm.getLine1Number();
433            sLocalPhoneName = tm.getLine1AlphaTag();
434            if (TextUtils.isEmpty(sLocalPhoneName)) {
435                sLocalPhoneName = this.getString(R.string.localPhoneName);
436            }
437        }
438
439        mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
440        synchronized (this) {
441            mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
442            mAuth.setChallenged(false);
443            mAuth.setCancelled(false);
444        }
445        BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket);
446        mServerSession = new ServerSession(transport, mPbapServer, mAuth);
447        setState(BluetoothPbap.STATE_CONNECTED);
448        if (VERBOSE) {
449            Log.v(TAG, "startObexServerSession() success!");
450        }
451    }
452
453    private void stopObexServerSession() {
454        if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
455
456        // Release the wake lock if obex transaction is over
457        if (mWakeLock != null) {
458            mWakeLock.release();
459            mWakeLock = null;
460        }
461
462        if (mServerSession != null) {
463            mServerSession.close();
464            mServerSession = null;
465        }
466
467        mAcceptThread = null;
468
469        try {
470            closeSocket(false, true);
471            mConnSocket = null;
472        } catch (IOException e) {
473            Log.e(TAG, "closeSocket error: " + e.toString());
474        }
475        // Last obex transaction is finished, we start to listen for incoming
476        // connection again
477        if (mAdapter.isEnabled()) {
478            startRfcommSocketListener();
479        }
480        setState(BluetoothPbap.STATE_DISCONNECTED);
481    }
482
483    private void notifyAuthKeyInput(final String key) {
484        synchronized (mAuth) {
485            if (key != null) {
486                mAuth.setSessionKey(key);
487            }
488            mAuth.setChallenged(true);
489            mAuth.notify();
490        }
491    }
492
493    private void notifyAuthCancelled() {
494        synchronized (mAuth) {
495            mAuth.setCancelled(true);
496            mAuth.notify();
497        }
498    }
499
500    /**
501     * A thread that runs in the background waiting for remote rfcomm
502     * connect.Once a remote socket connected, this thread shall be
503     * shutdown.When the remote disconnect,this thread shall run again waiting
504     * for next request.
505     */
506    private class SocketAcceptThread extends Thread {
507
508        private boolean stopped = false;
509
510        @Override
511        public void run() {
512            while (!stopped) {
513                try {
514                    mConnSocket = mServerSocket.accept();
515
516                    mRemoteDevice = mConnSocket.getRemoteDevice();
517                    if (mRemoteDevice == null) {
518                        Log.i(TAG, "getRemoteDevice() = null");
519                        break;
520                    }
521                    sRemoteDeviceName = mRemoteDevice.getName();
522                    // In case getRemoteName failed and return null
523                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
524                        sRemoteDeviceName = getString(R.string.defaultname);
525                    }
526                    boolean trust = mRemoteDevice.getTrustState();
527                    if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
528
529                    if (trust) {
530                        try {
531                            if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
532                                + sRemoteDeviceName + " automatically as trusted device");
533                            startObexServerSession();
534                        } catch (IOException ex) {
535                            Log.e(TAG, "catch exception starting obex server session"
536                                    + ex.toString());
537                        }
538                    } else {
539                        Intent intent = new
540                            Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
541                        intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
542                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
543                                        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
544                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
545                        intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
546                        intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME,
547                                        BluetoothPbapReceiver.class.getName());
548                        sendBroadcast(intent, BLUETOOTH_PERM);
549                        isWaitingAuthorization = true;
550
551                        if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
552                                + sRemoteDeviceName);
553
554                        // In case car kit time out and try to use HFP for
555                        // phonebook
556                        // access, while UI still there waiting for user to
557                        // confirm
558                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
559                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
560                    }
561                    stopped = true; // job done ,close this thread;
562                } catch (IOException ex) {
563                    if (stopped) {
564                        break;
565                    }
566                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
567                }
568            }
569        }
570
571        void shutdown() {
572            stopped = true;
573            interrupt();
574        }
575    }
576
577    private final Handler mSessionStatusHandler = new Handler() {
578        @Override
579        public void handleMessage(Message msg) {
580            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
581
582            switch (msg.what) {
583                case START_LISTENER:
584                    if (mAdapter.isEnabled()) {
585                        startRfcommSocketListener();
586                    } else {
587                        closeService();// release all resources
588                    }
589                    break;
590                case USER_TIMEOUT:
591                    Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
592                    intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
593                    sendBroadcast(intent);
594                    isWaitingAuthorization = false;
595                    stopObexServerSession();
596                    break;
597                case AUTH_TIMEOUT:
598                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
599                    sendBroadcast(i);
600                    removePbapNotification(NOTIFICATION_ID_AUTH);
601                    notifyAuthCancelled();
602                    break;
603                case MSG_SERVERSESSION_CLOSE:
604                    stopObexServerSession();
605                    break;
606                case MSG_SESSION_ESTABLISHED:
607                    break;
608                case MSG_SESSION_DISCONNECTED:
609                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
610                    break;
611                case MSG_OBEX_AUTH_CHALL:
612                    createPbapNotification(AUTH_CHALL_ACTION);
613                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
614                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
615                    break;
616                default:
617                    break;
618            }
619        }
620    };
621
622    private void setState(int state) {
623        setState(state, BluetoothPbap.RESULT_SUCCESS);
624    }
625
626    private synchronized void setState(int state, int result) {
627        if (state != mState) {
628            if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
629                    + result);
630            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
631            intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, mState);
632            mState = state;
633            intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
634            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
635            sendBroadcast(intent, BLUETOOTH_PERM);
636            try {
637                mBluetoothService.sendConnectionStateChange(mRemoteDevice, BluetoothProfile.PBAP,
638                                                            mState, state);
639            } catch (RemoteException e) {
640                Log.e(TAG, "RemoteException in sendConnectionStateChange");
641            }
642        }
643    }
644
645    private void createPbapNotification(String action) {
646
647        NotificationManager nm = (NotificationManager)
648            getSystemService(Context.NOTIFICATION_SERVICE);
649
650        // Create an intent triggered by clicking on the status icon.
651        Intent clickIntent = new Intent();
652        clickIntent.setClass(this, BluetoothPbapActivity.class);
653        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
654        clickIntent.setAction(action);
655
656        // Create an intent triggered by clicking on the
657        // "Clear All Notifications" button
658        Intent deleteIntent = new Intent();
659        deleteIntent.setClass(this, BluetoothPbapReceiver.class);
660
661        Notification notification = null;
662        String name = getRemoteDeviceName();
663
664        if (action.equals(AUTH_CHALL_ACTION)) {
665            deleteIntent.setAction(AUTH_CANCELLED_ACTION);
666            notification = new Notification(android.R.drawable.stat_sys_data_bluetooth,
667                getString(R.string.auth_notif_ticker), System.currentTimeMillis());
668            notification.setLatestEventInfo(this, getString(R.string.auth_notif_title),
669                    getString(R.string.auth_notif_message, name), PendingIntent
670                            .getActivity(this, 0, clickIntent, 0));
671
672            notification.flags |= Notification.FLAG_AUTO_CANCEL;
673            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
674            notification.defaults = Notification.DEFAULT_SOUND;
675            notification.deleteIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, 0);
676            nm.notify(NOTIFICATION_ID_AUTH, notification);
677        }
678    }
679
680    private void removePbapNotification(int id) {
681        NotificationManager nm = (NotificationManager)
682            getSystemService(Context.NOTIFICATION_SERVICE);
683        nm.cancel(id);
684    }
685
686    public static String getLocalPhoneNum() {
687        return sLocalPhoneNum;
688    }
689
690    public static String getLocalPhoneName() {
691        return sLocalPhoneName;
692    }
693
694    public static String getRemoteDeviceName() {
695        return sRemoteDeviceName;
696    }
697
698    /**
699     * Handlers for incoming service calls
700     */
701    private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() {
702        public int getState() {
703            if (DEBUG) Log.d(TAG, "getState " + mState);
704
705            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
706            return mState;
707        }
708
709        public BluetoothDevice getClient() {
710            if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice);
711
712            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
713            if (mState == BluetoothPbap.STATE_DISCONNECTED) {
714                return null;
715            }
716            return mRemoteDevice;
717        }
718
719        public boolean isConnected(BluetoothDevice device) {
720            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
721            return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device);
722        }
723
724        public boolean connect(BluetoothDevice device) {
725            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
726                    "Need BLUETOOTH_ADMIN permission");
727            return false;
728        }
729
730        public void disconnect() {
731            if (DEBUG) Log.d(TAG, "disconnect");
732
733            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
734                    "Need BLUETOOTH_ADMIN permission");
735            synchronized (BluetoothPbapService.this) {
736                switch (mState) {
737                    case BluetoothPbap.STATE_CONNECTED:
738                        if (mServerSession != null) {
739                            mServerSession.close();
740                            mServerSession = null;
741                        }
742                        try {
743                            closeSocket(false, true);
744                            mConnSocket = null;
745                        } catch (IOException ex) {
746                            Log.e(TAG, "Caught the error: " + ex);
747                        }
748                        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
749                        break;
750                    default:
751                        break;
752                }
753            }
754        }
755    };
756}
757