BluetoothPbapService.java revision f2c447b81c6c03cb1c8a3e64a10381e23934834a
1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.android.bluetooth.pbap;
34
35import com.android.bluetooth.R;
36
37import android.app.Notification;
38import android.app.NotificationManager;
39import android.app.PendingIntent;
40import android.app.Service;
41import android.content.Context;
42import android.content.Intent;
43import android.content.res.Resources;
44import android.bluetooth.BluetoothAdapter;
45import android.bluetooth.BluetoothDevice;
46import android.bluetooth.BluetoothPbap;
47import android.bluetooth.BluetoothSocket;
48import android.bluetooth.BluetoothServerSocket;
49import android.bluetooth.IBluetoothPbap;
50import android.os.Handler;
51import android.os.IBinder;
52import android.os.Message;
53import android.os.PowerManager;
54import android.telephony.TelephonyManager;
55import android.util.Log;
56import android.widget.Toast;
57
58import java.io.IOException;
59import java.util.ArrayList;
60
61import javax.obex.ServerSession;
62
63public class BluetoothPbapService extends Service {
64
65    private static final String TAG = "BluetoothPbapService";
66
67    /**
68     * To enable PBAP debug logging - run below cmd in adb shell, and restart
69     * com.android.bluetooth process. only enable VERBOSE log:
70     * "setprop log.tag.BluetoothPbapService VERBOSE"; enable both VERBOSE and DEBUG log:
71     * "setprop log.tag.BluetoothPbapService DEBUG"
72     */
73
74    public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
75
76    public static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE)
77            || Log.isLoggable(TAG, Log.DEBUG);
78
79    /**
80     * Intent indicating incoming connection request which is sent to
81     * BluetoothPbapActivity
82     */
83    public static final String ACCESS_REQUEST_ACTION = "com.android.bluetooth.pbap.accessrequest";
84
85    /**
86     * Intent indicating incoming connection request accepted by user which is
87     * sent from BluetoothPbapActivity
88     */
89    public static final String ACCESS_ALLOWED_ACTION = "com.android.bluetooth.pbap.accessallowed";
90
91    /**
92     * Intent indicating incoming connection request denied by user which is
93     * sent from BluetoothPbapActivity
94     */
95    public static final String ACCESS_DISALLOWED_ACTION =
96            "com.android.bluetooth.pbap.accessdisallowed";
97
98    /**
99     * Intent indicating incoming obex authentication request which is from
100     * PCE(Carkit)
101     */
102    public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall";
103
104    /**
105     * Intent indicating obex session key input complete by user which is sent
106     * from BluetoothPbapActivity
107     */
108    public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse";
109
110    /**
111     * Intent indicating user canceled obex authentication session key input
112     * which is sent from BluetoothPbapActivity
113     */
114    public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled";
115
116    /**
117     * Intent indicating timeout for user confirmation, which is sent to
118     * BluetoothPbapActivity
119     */
120    public static final String USER_CONFIRM_TIMEOUT_ACTION =
121            "com.android.bluetooth.pbap.userconfirmtimeout";
122
123    /**
124     * Intent Extra name indicating always allowed which is sent from
125     * BluetoothPbapActivity
126     */
127    public static final String EXTRA_ALWAYS_ALLOWED = "com.android.bluetooth.pbap.alwaysallowed";
128
129    /**
130     * Intent Extra name indicating session key which is sent from
131     * BluetoothPbapActivity
132     */
133    public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
134
135    public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
136
137    public static final int MSG_SERVERSESSION_CLOSE = 5000;
138
139    public static final int MSG_SESSION_ESTABLISHED = 5001;
140
141    public static final int MSG_SESSION_DISCONNECTED = 5002;
142
143    public static final int MSG_OBEX_AUTH_CHALL = 5003;
144
145    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
146
147    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
148
149    private static final int START_LISTENER = 1;
150
151    private static final int USER_TIMEOUT = 2;
152
153    private static final int AUTH_TIMEOUT = 3;
154
155    private static final int PORT_NUM = 19;
156
157    private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
158
159    private static final int TIME_TO_WAIT_VALUE = 6000;
160
161    // Ensure not conflict with Opp notification ID
162    private static final int NOTIFICATION_ID_ACCESS = -1000001;
163
164    private static final int NOTIFICATION_ID_AUTH = -1000002;
165
166    private PowerManager.WakeLock mWakeLock = null;
167
168    private BluetoothAdapter mAdapter;
169
170    private SocketAcceptThread mAcceptThread = null;
171
172    private BluetoothPbapAuthenticator mAuth = null;
173
174    private BluetoothPbapObexServer mPbapServer;
175
176    private static BluetoothPbapVcardManager sVcardManager;
177
178    private ServerSession mServerSession = null;
179
180    private BluetoothServerSocket mServerSocket = null;
181
182    private BluetoothSocket mConnSocket = null;
183
184    private BluetoothDevice mRemoteDevice = null;
185
186    private static String sLocalPhoneNum = null;
187
188    private static String sLocalPhoneName = null;
189
190    private static String sRemoteDeviceName = null;
191
192    private boolean mHasStarted = false;
193
194    private volatile boolean mInterrupted;
195
196    private int mState;
197
198    private int mStartId = -1;
199
200    public BluetoothPbapService() {
201        mState = BluetoothPbap.STATE_DISCONNECTED;
202    }
203
204    @Override
205    public void onCreate() {
206        super.onCreate();
207        if (VERBOSE) Log.v(TAG, "Pbap Service onCreate");
208
209        mInterrupted = false;
210        mAdapter = (BluetoothAdapter)getSystemService(Context.BLUETOOTH_SERVICE);
211        sVcardManager = new BluetoothPbapVcardManager(BluetoothPbapService.this);
212        if (!mHasStarted) {
213            mHasStarted = true;
214            if (VERBOSE) Log.v(TAG, "Starting PBAP service");
215
216            int state = mAdapter.getState();
217            if (state == BluetoothAdapter.STATE_ON) {
218                mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
219                        .obtainMessage(START_LISTENER), TIME_TO_WAIT_VALUE);
220            }
221        }
222    }
223
224    @Override
225    public void onStart(Intent intent, int startId) {
226        if (VERBOSE) Log.v(TAG, "Pbap Service onStart");
227
228        mStartId = startId;
229        if (mAdapter == null) {
230            Log.w(TAG, "Stopping BluetoothPbapService: "
231                    + "device does not have BT or device is not ready");
232            // Release all resources
233            closeService();
234        } else {
235            parseIntent(intent);
236        }
237    }
238
239    // process the intent from receiver
240    private void parseIntent(final Intent intent) {
241        String action = intent.getExtras().getString("action");
242        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
243        boolean removeTimeoutMsg = true;
244        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
245            removeTimeoutMsg = false;
246            if (state == BluetoothAdapter.STATE_OFF) {
247                // Release all resources
248                closeService();
249            }
250        } else if (action.equals(ACCESS_ALLOWED_ACTION)) {
251            if (intent.getBooleanExtra(EXTRA_ALWAYS_ALLOWED, false)) {
252                boolean result = mRemoteDevice.setTrust(true);
253                if (VERBOSE) Log.v(TAG, "setTrust() result=" + result);
254            }
255            try {
256                if (mConnSocket != null) {
257                    startObexServerSession();
258                } else {
259                    stopObexServerSession();
260                }
261            } catch (IOException ex) {
262                Log.e(TAG, "Caught the error: " + ex.toString());
263            }
264        } else if (action.equals(ACCESS_DISALLOWED_ACTION)) {
265            stopObexServerSession();
266        } else if (action.equals(AUTH_RESPONSE_ACTION)) {
267            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
268            notifyAuthKeyInput(sessionkey);
269        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
270            notifyAuthCancelled();
271        } else {
272            removeTimeoutMsg = false;
273        }
274
275        if (removeTimeoutMsg) {
276            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
277        }
278    }
279
280    @Override
281    public void onDestroy() {
282        if (VERBOSE) Log.v(TAG, "Pbap Service onDestroy");
283
284        super.onDestroy();
285        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
286        if (mWakeLock != null) {
287            mWakeLock.release();
288            mWakeLock = null;
289        }
290    }
291
292    @Override
293    public IBinder onBind(Intent intent) {
294        if (VERBOSE) Log.v(TAG, "Pbap Service onBind");
295        return mBinder;
296    }
297
298    private void startRfcommSocketListener() {
299        if (VERBOSE) Log.v(TAG, "Pbap Service startRfcommSocketListener");
300
301        if (mServerSocket == null) {
302            if (!initSocket()) {
303                closeService();
304                return;
305            }
306        }
307        if (mAcceptThread == null) {
308            mAcceptThread = new SocketAcceptThread();
309            mAcceptThread.setName("BluetoothPbapAcceptThread");
310            mAcceptThread.start();
311        }
312    }
313
314    private final boolean initSocket() {
315        if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
316
317        boolean initSocketOK = true;
318        final int CREATE_RETRY_TIME = 10;
319
320        // It's possible that create will fail in some cases. retry for 10 times
321        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
322            try {
323                // It is mandatory for PSE to support initiation of bonding and
324                // encryption.
325                mServerSocket = mAdapter.listenUsingRfcommOn(PORT_NUM);
326            } catch (IOException e) {
327                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
328                initSocketOK = false;
329            }
330            if (!initSocketOK) {
331                synchronized (this) {
332                    try {
333                        if (VERBOSE) Log.v(TAG, "wait 3 seconds");
334                        Thread.sleep(3000);
335                    } catch (InterruptedException e) {
336                        Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
337                        mInterrupted = true;
338                    }
339                }
340            } else {
341                break;
342            }
343        }
344
345        if (initSocketOK) {
346            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket on channel " + PORT_NUM);
347
348        } else {
349            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
350        }
351        return initSocketOK;
352    }
353
354    private final void closeSocket(boolean server, boolean accept) throws IOException {
355        if (server == true) {
356            // Stop the possible trying to init serverSocket
357            mInterrupted = true;
358
359            if (mServerSocket != null) {
360                mServerSocket.close();
361            }
362            mServerSocket = null;
363        }
364
365        if (accept == true) {
366            if (mConnSocket != null) {
367                mConnSocket.close();
368            }
369            mConnSocket = null;
370        }
371    }
372
373    private final void closeService() {
374        if (VERBOSE) Log.v(TAG, "Pbap Service closeService");
375
376        try {
377            closeSocket(true, true);
378        } catch (IOException ex) {
379            Log.e(TAG, "CloseSocket error: " + ex);
380        }
381
382        if (mAcceptThread != null) {
383            try {
384                mAcceptThread.shutdown();
385                mAcceptThread.join();
386                mAcceptThread = null;
387            } catch (InterruptedException ex) {
388                Log.w(TAG, "mAcceptThread close error" + ex);
389            }
390        }
391        if (mServerSession != null) {
392            mServerSession.close();
393            mServerSession = null;
394        }
395
396        mHasStarted = false;
397        if (stopSelfResult(mStartId)) {
398            if (VERBOSE) Log.v(TAG, "successfully stopped pbap service");
399        }
400    }
401
402    private final void startObexServerSession() throws IOException {
403        if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
404
405        // acquire the wakeLock before start Obex transaction thread
406        if (mWakeLock == null) {
407            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
408            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
409                    "StartingObexPbapTransaction");
410            mWakeLock.setReferenceCounted(false);
411            mWakeLock.acquire();
412        }
413        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
414        if (tm != null) {
415            sLocalPhoneNum = tm.getLine1Number();
416            sLocalPhoneName = tm.getLine1AlphaTag();
417        }
418        mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler);
419        synchronized (this) {
420            mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
421            mAuth.setChallenged(false);
422            mAuth.setCancelled(false);
423        }
424        BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket);
425        mServerSession = new ServerSession(transport, mPbapServer, mAuth);
426        setState(BluetoothPbap.STATE_CONNECTED);
427        if (VERBOSE) {
428            Log.v(TAG, "startObexServerSession() success!");
429        }
430    }
431
432    private void stopObexServerSession() {
433        if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
434
435        // Release the wake lock if obex transaction is over
436        if (mWakeLock != null) {
437            mWakeLock.release();
438            mWakeLock = null;
439        }
440
441        if (mServerSession != null) {
442            mServerSession.close();
443            mServerSession = null;
444        }
445
446        mAcceptThread = null;
447
448        try {
449            closeSocket(false, true);
450        } catch (IOException e) {
451            Log.e(TAG, "closeSocket error: " + e.toString());
452        }
453        // Last obex transaction is finished, we start to listen for incoming
454        // connection again
455        if (mAdapter.isEnabled()) {
456            startRfcommSocketListener();
457        }
458        setState(BluetoothPbap.STATE_DISCONNECTED);
459    }
460
461    private void notifyAuthKeyInput(final String key) {
462        synchronized (this) {
463            mAuth.setSessionKey(key);
464            mAuth.setChallenged(true);
465            mAuth.notify();
466        }
467    }
468
469    private void notifyAuthCancelled() {
470        synchronized (this) {
471            mAuth.setCancelled(true);
472            mAuth.notify();
473        }
474    }
475
476    /**
477     * A thread that runs in the background waiting for remote rfcomm
478     * connect.Once a remote socket connected, this thread shall be
479     * shutdown.When the remote disconnect,this thread shall run again waiting
480     * for next request.
481     */
482    private class SocketAcceptThread extends Thread {
483
484        private boolean stopped = false;
485
486        @Override
487        public void run() {
488            while (!stopped) {
489                try {
490                    mConnSocket = mServerSocket.accept();
491
492                    mRemoteDevice = mConnSocket.getRemoteDevice();
493                    if (mRemoteDevice != null) {
494                        sRemoteDeviceName = mRemoteDevice.getName();
495                        // In case getRemoteName failed and return null
496                        if (sRemoteDeviceName == null) {
497                            sRemoteDeviceName = getString(R.string.defaultname);
498                        }
499                    }
500                    boolean trust = mRemoteDevice.getTrustState();
501                    if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust);
502
503                    if (trust) {
504                        try {
505                            if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
506                                + sRemoteDeviceName + " automatically as trusted device");
507                            startObexServerSession();
508                        } catch (IOException ex) {
509                            Log.e(TAG, "catch exception starting obex server session"
510                                    + ex.toString());
511                        }
512                    } else {
513                        createPbapNotification(ACCESS_REQUEST_ACTION);
514                        if (VERBOSE) Log.v(TAG, "incomming connection accepted from: "
515                                + sRemoteDeviceName);
516
517                        // In case car kit time out and try to use HFP for
518                        // phonebook
519                        // access, while UI still there waiting for user to
520                        // confirm
521                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
522                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
523                    }
524                    stopped = true; // job done ,close this thread;
525                } catch (IOException ex) {
526                    if (stopped) {
527                        break;
528                    }
529                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
530                }
531            }
532        }
533
534        void shutdown() {
535            stopped = true;
536            interrupt();
537        }
538    }
539
540    private final Handler mSessionStatusHandler = new Handler() {
541        @Override
542        public void handleMessage(Message msg) {
543            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
544
545            CharSequence tmpTxt;
546            switch (msg.what) {
547                case START_LISTENER:
548                    if (mAdapter.isEnabled()) {
549                        startRfcommSocketListener();
550                    } else {
551                        closeService();// release all resources
552                    }
553                    break;
554                case USER_TIMEOUT:
555                    Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
556                    sendBroadcast(intent);
557                    removePbapNotification(NOTIFICATION_ID_ACCESS);
558                    stopObexServerSession();
559                    break;
560                case AUTH_TIMEOUT:
561                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
562                    sendBroadcast(i);
563                    removePbapNotification(NOTIFICATION_ID_AUTH);
564                    notifyAuthCancelled();
565                    break;
566                case MSG_SERVERSESSION_CLOSE:
567                    stopObexServerSession();
568                    tmpTxt = getString(R.string.toast_disconnected, sRemoteDeviceName);
569                    Toast.makeText(BluetoothPbapService.this, tmpTxt, Toast.LENGTH_SHORT).show();
570                    break;
571                case MSG_SESSION_ESTABLISHED:
572                    tmpTxt = getString(R.string.toast_connected, sRemoteDeviceName);
573                    Toast.makeText(BluetoothPbapService.this, tmpTxt, Toast.LENGTH_SHORT).show();
574                    break;
575                case MSG_SESSION_DISCONNECTED:
576                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
577                    break;
578                case MSG_OBEX_AUTH_CHALL:
579                    createPbapNotification(AUTH_CHALL_ACTION);
580                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
581                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
582                    break;
583                default:
584                    break;
585            }
586        }
587    };
588
589    private void setState(int state) {
590        setState(state, BluetoothPbap.RESULT_SUCCESS);
591    }
592
593    private synchronized void setState(int state, int result) {
594        if (state != mState) {
595            if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
596                    + result);
597            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
598            intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, mState);
599            mState = state;
600            intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
601            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
602            sendBroadcast(intent, BLUETOOTH_PERM);
603        }
604    }
605
606    private void createPbapNotification(String action) {
607        Context context = getApplicationContext();
608
609        NotificationManager nm = (NotificationManager)context
610                .getSystemService(Context.NOTIFICATION_SERVICE);
611
612        // Create an intent triggered by clicking on the status icon.
613        Intent clickIntent = new Intent();
614        clickIntent.setClass(context, BluetoothPbapActivity.class);
615        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
616        clickIntent.setAction(action);
617
618        // Create an intent triggered by clicking on the
619        // "Clear All Notifications" button
620        Intent deleteIntent = new Intent();
621        deleteIntent.setClass(context, BluetoothPbapReceiver.class);
622
623        Notification notification = null;
624        String name = getRemoteDeviceName();
625        if (action.equals(ACCESS_REQUEST_ACTION)) {
626            deleteIntent.setAction(ACCESS_DISALLOWED_ACTION);
627            notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, context
628                    .getString(R.string.pbap_notif_ticker), System.currentTimeMillis());
629            notification.setLatestEventInfo(context, context.getString(R.string.pbap_notif_title),
630                    context.getString(R.string.pbap_notif_message, name), PendingIntent
631                            .getActivity(context, 0, clickIntent, 0));
632
633            notification.flags |= Notification.FLAG_AUTO_CANCEL;
634            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
635            notification.defaults = Notification.DEFAULT_SOUND;
636            notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
637            nm.notify(NOTIFICATION_ID_ACCESS, notification);
638        } else if (action.equals(AUTH_CHALL_ACTION)) {
639            deleteIntent.setAction(AUTH_CANCELLED_ACTION);
640            notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, context
641                    .getString(R.string.auth_notif_ticker), System.currentTimeMillis());
642            notification.setLatestEventInfo(context, context.getString(R.string.auth_notif_title),
643                    context.getString(R.string.auth_notif_message, name), PendingIntent
644                            .getActivity(context, 0, clickIntent, 0));
645
646            notification.flags |= Notification.FLAG_AUTO_CANCEL;
647            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
648            notification.defaults = Notification.DEFAULT_SOUND;
649            notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
650            nm.notify(NOTIFICATION_ID_AUTH, notification);
651        }
652    }
653
654    private void removePbapNotification(int id) {
655        Context context = getApplicationContext();
656        NotificationManager nm = (NotificationManager)context
657                .getSystemService(Context.NOTIFICATION_SERVICE);
658        nm.cancel(id);
659    }
660
661    public static int getPhonebookSize(final int type) {
662        int size;
663        switch (type) {
664            case BluetoothPbapObexServer.ContentType.PHONEBOOK:
665                size = sVcardManager.getPhonebookSize();
666                break;
667            default:
668                size = sVcardManager.getCallHistorySize(type);
669                break;
670        }
671        if (VERBOSE) Log.v(TAG, "getPhonebookSzie size = " + size + " type = " + type);
672        return size;
673    }
674
675    public static void closeContactsCursor(final int type) {
676        if (VERBOSE) Log.v(TAG, "closeContactsCursor: type=" + type);
677
678        switch (type) {
679            case BluetoothPbapObexServer.ContentType.PHONEBOOK:
680                sVcardManager.closeContactsCursor();
681                break;
682            default:
683                sVcardManager.closeCallLogCursor();
684                break;
685        }
686    }
687
688    public static int queryDataFromContactsDB(final int type) {
689        if (VERBOSE) Log.v(TAG, "queryDataFromContactsDB: type=" + type);
690
691        int queryResult = 0;
692        switch (type) {
693            case BluetoothPbapObexServer.ContentType.PHONEBOOK:
694                queryResult = sVcardManager.queryDataFromContactsDB();
695                break;
696            default:
697                queryResult = sVcardManager.queryDataFromCallLogDB(type);
698                break;
699        }
700        return queryResult;
701    }
702
703    public static String getPhonebook(final int type, final int pos, final boolean vcardType) {
704        if (VERBOSE) Log.v(TAG, "getPhonebook type=" + type + " pos=" + pos + " vcardType="
705                + vcardType);
706
707        String vCardStr;
708        switch (type) {
709            case BluetoothPbapObexServer.ContentType.PHONEBOOK:
710                vCardStr = sVcardManager.getPhonebook(pos, vcardType);
711                break;
712            default:
713                vCardStr = sVcardManager.getCallHistory(pos, vcardType);
714                break;
715        }
716        return vCardStr;
717    }
718
719    public static String getLocalPhoneNum() {
720        return sLocalPhoneNum;
721    }
722
723    public static String getLocalPhoneName() {
724        return sLocalPhoneName;
725    }
726
727    public static String getRemoteDeviceName() {
728        return sRemoteDeviceName;
729    }
730
731    // Used for phone book listing by name
732    public static ArrayList<String> getPhonebookNameList() {
733        return sVcardManager.loadNameList();
734    }
735
736    // Used for phone book listing by number
737    public static ArrayList<String> getPhonebookNumberList() {
738        return sVcardManager.loadNumberList();
739    }
740
741    // Used for call history listing
742    public static ArrayList<String> getCallLogList(final int type) {
743        return sVcardManager.loadCallHistoryList(type);
744    }
745
746    /**
747     * Handlers for incoming service calls
748     */
749    private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() {
750        public int getState() {
751            if (DEBUG) Log.d(TAG, "getState " + mState);
752
753            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
754            return mState;
755        }
756
757        public BluetoothDevice getClient() {
758            if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice);
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            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
769            return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device);
770        }
771
772        public boolean connect(BluetoothDevice device) {
773            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
774                    "Need BLUETOOTH_ADMIN permission");
775            return false;
776        }
777
778        public void disconnect() {
779            if (DEBUG) Log.d(TAG, "disconnect");
780
781            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
782                    "Need BLUETOOTH_ADMIN permission");
783            synchronized (BluetoothPbapService.this) {
784                switch (mState) {
785                    case BluetoothPbap.STATE_CONNECTED:
786                        if (mServerSession != null) {
787                            mServerSession.close();
788                            mServerSession = null;
789                        }
790                        try {
791                            closeSocket(false, true);
792                        } catch (IOException ex) {
793                            Log.e(TAG, "Caught the error: " + ex);
794                        }
795                        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
796                        break;
797                    default:
798                        break;
799                }
800            }
801        }
802    };
803}
804