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