BluetoothPbapService.java revision acab258c177d82338b1696360cd0b8c9821b0f03
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.Service;
38import android.content.Context;
39import android.content.Intent;
40import android.bluetooth.BluetoothAdapter;
41import android.bluetooth.BluetoothDevice;
42import android.bluetooth.BluetoothError;
43import android.bluetooth.BluetoothIntent;
44import android.bluetooth.BluetoothPbap;
45import android.bluetooth.BluetoothSocket;
46import android.bluetooth.BluetoothServerSocket;
47import android.bluetooth.IBluetoothPbap;
48import android.os.Handler;
49import android.os.IBinder;
50import android.os.Message;
51import android.os.PowerManager;
52import android.telephony.TelephonyManager;
53import android.util.Log;
54import android.widget.Toast;
55
56import java.io.IOException;
57import java.util.ArrayList;
58
59import javax.obex.ServerSession;
60
61public class BluetoothPbapService extends Service {
62
63    private static final String TAG = "BluetoothPbapService";
64
65    public static final boolean DBG = false;
66
67    /**
68     * Intent indicating incoming connection request which is sent to
69     * BluetoothPbapActivity
70     */
71    public static final String ACCESS_REQUEST_ACTION = "com.android.bluetooth.pbap.accessrequest";
72
73    /**
74     * Intent indicating incoming connection request accepted by user which is
75     * sent from BluetoothPbapActivity
76     */
77    public static final String ACCESS_ALLOWED_ACTION = "com.android.bluetooth.pbap.accessallowed";
78
79    /**
80     * Intent indicating incoming connection request denied by user which is
81     * sent from BluetoothPbapActivity
82     */
83    public static final String ACCESS_DISALLOWED_ACTION =
84        "com.android.bluetooth.pbap.accessdisallowed";
85
86    /**
87     * Intent indicating incoming obex authentication request which is from
88     * PCE(Carkit)
89     */
90    public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall";
91
92    /**
93     * Intent indicating obex session key input complete by user which is sent
94     * from BluetoothPbapActivity
95     */
96    public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse";
97
98    /**
99     * Intent indicating user canceled obex authentication session key input
100     * which is sent from BluetoothPbapActivity
101     */
102    public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled";
103
104    /**
105     * Intent indicating timeout for user confirmation, which is sent to
106     * BluetoothPbapActivity
107     */
108    public static final String USER_CONFIRM_TIMEOUT_ACTION =
109        "com.android.bluetooth.pbap.userconfirmtimeout";
110
111    /**
112     * Intent Extra name indicating always allowed which is sent from
113     * BluetoothPbapActivity
114     */
115    public static final String EXTRA_ALWAYS_ALLOWED = "com.android.bluetooth.pbap.alwaysallowed";
116
117    /**
118     * Intent Extra name indicating session key which is sent from
119     * BluetoothPbapActivity
120     */
121    public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
122
123    public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
124
125    public static final int MSG_SERVERSESSION_CLOSE = 5000;
126
127    public static final int MSG_SESSION_ESTABLISHED = 5001;
128
129    public static final int MSG_SESSION_DISCONNECTED = 5002;
130
131    public static final int MSG_OBEX_AUTH_CHALL = 5003;
132
133    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
134
135    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
136
137    private static final int START_LISTENER = 1;
138
139    private static final int USER_TIMEOUT = 2;
140
141    private static final int AUTH_TIMEOUT = 3;
142
143    private PowerManager.WakeLock mWakeLock = null;
144
145    private BluetoothAdapter mAdapter;
146
147    private SocketAcceptThread mAcceptThread = null;
148
149    private BluetoothPbapObexServer mPbapServer;
150
151    private ServerSession mServerSession = null;
152
153    private BluetoothServerSocket mServerSocket = null;
154
155    private BluetoothSocket mConnSocket = null;
156
157    private String mDeviceAddr = null;
158
159    private static String sLocalPhoneNum = null;
160
161    private static String sLocalPhoneName = null;
162
163    private static String sRemoteDeviceName = null;
164
165    private BluetoothDevice mRemoteDevice = null;
166
167    private boolean mHasStarted = false;
168
169    private int mState;
170
171    private int mStartId = -1;
172
173    private static final int PORT_NUM = 19;
174
175    private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
176
177    private static final int SOCKET_ACCEPT_TIMEOUT_VALUE = 5000;
178
179    private static final int TIME_TO_WAIT_VALUE = 6000;
180
181    private static BluetoothPbapVcardManager sVcardManager;
182
183    private CharSequence mTmpTxt;
184
185    private BluetoothPbapAuthenticator mAuth = null;
186
187    public BluetoothPbapService() {
188        mState = BluetoothPbap.STATE_DISCONNECTED;
189    }
190
191    @Override
192    public void onCreate() {
193        super.onCreate();
194        mAdapter = (BluetoothAdapter) getSystemService(Context.BLUETOOTH_SERVICE);
195        if (mAdapter != null) {
196            mDeviceAddr = mAdapter.getAddress();
197        }
198        sVcardManager = new BluetoothPbapVcardManager(BluetoothPbapService.this);
199        if (!mHasStarted && mDeviceAddr != null) {
200            mHasStarted = true;
201            Log.i(TAG, "Starting PBAP service");
202            int state = mAdapter.getBluetoothState();
203            if (state == BluetoothAdapter.BLUETOOTH_STATE_ON) {
204                mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
205                        .obtainMessage(START_LISTENER), TIME_TO_WAIT_VALUE);
206            }
207        }
208    }
209
210    @Override
211    public void onStart(Intent intent, int startId) {
212        mStartId = startId;
213        if (mAdapter == null || mDeviceAddr == null) {
214            Log.w(TAG, "Stopping BluetoothPbapService: "
215                    + "device does not have BT or device is not ready");
216            closeService(); // release all resources
217        } else {
218            parseIntent(intent);
219        }
220    }
221
222    // process the intent from receiver
223    private void parseIntent(final Intent intent) {
224        String action = intent.getExtras().getString("action");
225        int state = intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE, BluetoothError.ERROR);
226        if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) {
227            if (state == BluetoothAdapter.BLUETOOTH_STATE_OFF) {
228                closeService(); // release all resources
229            }
230        } else if (action.equals(ACCESS_ALLOWED_ACTION)) {
231            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
232            if (intent.getBooleanExtra(EXTRA_ALWAYS_ALLOWED, false)) {
233                boolean result = mRemoteDevice.setTrust(true);
234                if (DBG) {
235                    Log.v(TAG, "setTrust() result=" + result);
236                }
237            }
238            try {
239                if (mConnSocket != null) {
240                    startObexServerSession();
241                } else {
242                    obexServerSessionClose();
243                }
244            } catch (IOException ex) {
245                if (DBG) {
246                    Log.e(TAG, "Caught the error: " + ex.toString());
247                }
248            }
249        } else if (action.equals(ACCESS_DISALLOWED_ACTION)) {
250            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
251            obexServerSessionClose();
252        } else if (action.equals(AUTH_RESPONSE_ACTION)) {
253            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
254            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
255            notifyAuthKeyInput(sessionkey);
256        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
257            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
258            notifyAuthCancelled();
259        }
260    }
261
262    @Override
263    public void onDestroy() {
264        super.onDestroy();
265        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
266    }
267
268    @Override
269    public IBinder onBind(Intent intent) {
270        return mBinder;
271    }
272
273    public static int getPhonebookSize(final int type) {
274        if (DBG) {
275            Log.d(TAG, "getPhonebookSzie type=" + type);
276        }
277        switch (type) {
278            case BluetoothPbapObexServer.ContentType.PHONEBOOK:
279                return sVcardManager.getPhonebookSize();
280            default:
281                return sVcardManager.getCallHistorySize(type);
282        }
283    }
284
285    public static String getPhonebook(final int type, final int pos, final boolean vcardType) {
286        if (DBG) {
287            Log.d(TAG, "getPhonebook type=" + type + " pos=" + pos + " vcardType=" + vcardType);
288        }
289        switch (type) {
290            case BluetoothPbapObexServer.ContentType.PHONEBOOK:
291                return sVcardManager.getPhonebook(pos, vcardType);
292            default:
293                return sVcardManager.getCallHistory(pos, type, vcardType);
294        }
295    }
296
297    public static String getLocalPhoneNum() {
298        return sLocalPhoneNum;
299    }
300
301    public static String getLocalPhoneName() {
302        return sLocalPhoneName;
303    }
304
305    public static String getRemoteDeviceName() {
306        return sRemoteDeviceName;
307    }
308
309    // Used for phone book listing by name
310    public static ArrayList<String> getPhonebookNameList() {
311        return sVcardManager.loadNameList();
312    }
313
314    // Used for phone book listing by number
315    public static ArrayList<String> getPhonebookNumberList() {
316        return sVcardManager.loadNumberList();
317    }
318
319    // Used for call history listing
320    public static ArrayList<String> getCallLogList(final int type) {
321        return sVcardManager.loadCallHistoryList(type);
322    }
323
324    private void notifyAuthKeyInput(final String key) {
325        synchronized (mAuth) {
326            mAuth.setSessionKey(key);
327            mAuth.setChallenged(true);
328            mAuth.notify();
329        }
330    }
331
332    private void notifyAuthCancelled() {
333        synchronized (mAuth) {
334            mAuth.setCancelled(true);
335            mAuth.notify();
336        }
337    }
338
339    private final boolean initSocket() {
340        try {
341            // It is mandatory for PSE to support initiation of bonding and
342            // encryption.
343            mServerSocket = mAdapter.listenUsingRfcommOn(PORT_NUM);
344        } catch (IOException ex) {
345            Log.e(TAG, "initSocket " + ex.toString());
346            return false;
347        }
348        return true;
349    }
350
351    private final void startObexServerSession() throws IOException {
352        // acquire the wakeLock before start Obex transaction thread
353        if (mWakeLock == null) {
354            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
355            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
356                    "StartingObexPbapTransaction");
357            mWakeLock.setReferenceCounted(false);
358            mWakeLock.acquire();
359        }
360        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
361        if (tm != null) {
362            sLocalPhoneNum = tm.getLine1Number();
363            sLocalPhoneName = tm.getLine1AlphaTag();
364        }
365        mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler);
366        mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
367        synchronized (mAuth) {
368            mAuth.setChallenged(false);
369            mAuth.setCancelled(false);
370        }
371        BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket);
372        mServerSession = new ServerSession(transport, mPbapServer, mAuth);
373        setState(BluetoothPbap.STATE_CONNECTED);
374        if (DBG) {
375            Log.v(TAG, "startObexServerSession() success!");
376        }
377    }
378
379    private final void closeSocket(boolean server, boolean accept) throws IOException {
380        if (server == true) {
381            if (mServerSocket != null) {
382                mServerSocket.close();
383            }
384            mServerSocket = null;
385        }
386
387        if (accept == true) {
388            if (mConnSocket != null) {
389                mConnSocket.close();
390            }
391            mConnSocket = null;
392        }
393    }
394
395    private final void closeService() {
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        if (mServerSession != null) {
406            mServerSession.close();
407            mServerSession = null;
408        }
409        try {
410            closeSocket(true, true);
411        } catch (IOException ex) {
412            if (DBG) {
413                Log.e(TAG, "Caught the error: " + ex);
414            }
415        }
416        mHasStarted = false;
417        BluetoothPbapReceiver.finishStartingService(BluetoothPbapService.this, mStartId);
418    }
419
420    private void obexServerSessionClose() {
421        // Release the wake lock if obex transaction is over
422        if (mWakeLock != null) {
423            mWakeLock.release();
424            mWakeLock = null;
425        }
426        mServerSession = null;
427        mAcceptThread = null;
428        try {
429            closeSocket(false, true);
430        } catch (IOException e) {
431            if (DBG) {
432                Log.e(TAG, "Caught the error: " + e.toString());
433            }
434        }
435        // Last obex transaction is finished,we start to listen for incoming
436        // connection again
437        if (mAdapter.isEnabled()) {
438            startRfcommSocketListener();
439        }
440        setState(BluetoothPbap.STATE_DISCONNECTED);
441    }
442
443    /**
444     * A thread that runs in the background waiting for remote rfcomm
445     * connect.Once a remote socket connected, this thread shall be
446     * shutdown.When the remote disconnect,this thread shall run again waiting
447     * for next request.
448     */
449    private class SocketAcceptThread extends Thread {
450
451        private boolean stopped = false;
452
453        @Override
454        public void run() {
455            while (!stopped) {
456                try {
457                    mConnSocket = mServerSocket.accept(SOCKET_ACCEPT_TIMEOUT_VALUE);
458                } catch (IOException ex) {
459                    if (stopped) {
460                        break;
461                    }
462                    if (DBG) {
463                        Log.v(TAG, "Caught the error in socketAcceptThread: " + ex);
464                    }
465                }
466                if (mConnSocket != null) {
467                    mRemoteDevice = mConnSocket.getRemoteDevice();
468                    if (mRemoteDevice != null) {
469                        sRemoteDeviceName = mRemoteDevice.getName();
470                        // In case getRemoteName failed and return null
471                        if (sRemoteDeviceName == null) {
472                            sRemoteDeviceName = getString(R.string.defaultname);
473                        }
474                    }
475                    boolean trust = mRemoteDevice.getTrustState();
476                    if (DBG) {
477                        Log.v(TAG, "GetTrustState() = " + trust);
478                    }
479                    if (trust) {
480                        try {
481                            if (DBG) {
482                                Log.v(TAG, "Trusted device, incomming connection accepted auto.");
483                            }
484                            startObexServerSession();
485                        } catch (IOException ex) {
486                            Log.e(TAG, "catch exception starting obex server session"
487                                    + ex.toString());
488                        }
489                    } else {
490                        BluetoothPbapReceiver.makeNewPbapNotification(getApplicationContext(),
491                                ACCESS_REQUEST_ACTION);
492                        if (DBG) {
493                            Log.v(TAG, "Incomming connection accepted from:" + sRemoteDeviceName);
494                        }
495                        // In case car kit time out and try to use HFP for phonebook
496                        // access, while UI still there waiting for user to confirm
497                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
498                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
499                    }
500                    stopped = true; // job done ,close this thread;
501                }
502            }
503        }
504
505        void shutdown() {
506            stopped = true;
507            interrupt();
508        }
509    }
510
511    private void startRfcommSocketListener() {
512        if (mServerSocket == null) {
513            if (!initSocket()) {
514                closeService();
515                return;
516            }
517        }
518        if (mAcceptThread == null) {
519            mAcceptThread = new SocketAcceptThread();
520            mAcceptThread.setName("BluetoothPbapAcceptThread");
521            mAcceptThread.start();
522        }
523    }
524
525    private void setState(int state) {
526        setState(state, BluetoothPbap.RESULT_SUCCESS);
527    }
528
529    private synchronized void setState(int state, int result) {
530        if (state != mState) {
531            if (DBG)
532                Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = " + result);
533            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
534            intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, mState);
535            mState = state;
536            intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
537            intent.putExtra(BluetoothIntent.DEVICE, mRemoteDevice);
538            sendBroadcast(intent, BLUETOOTH_PERM);
539        }
540    }
541
542    /**
543     * Handlers for incoming service calls
544     */
545    private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() {
546        public int getState() {
547            if (DBG) {
548                Log.d(TAG, "getState " + mState);
549            }
550            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
551            return mState;
552        }
553
554        public BluetoothDevice getClient() {
555            if (DBG) {
556                Log.d(TAG, "getClient" + mRemoteDevice);
557            }
558            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
559            if (mState == BluetoothPbap.STATE_DISCONNECTED) {
560                return null;
561            }
562            return mRemoteDevice;
563        }
564
565        public boolean isConnected(BluetoothDevice device) {
566            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
567            return mState == BluetoothPbap.STATE_CONNECTED && mRemoteDevice.equals(device);
568        }
569
570        public boolean connect(BluetoothDevice device) {
571            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
572                    "Need BLUETOOTH_ADMIN permission");
573            return false;
574        }
575
576        public void disconnect() {
577            if (DBG) {
578                Log.d(TAG, "disconnect");
579            }
580            enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
581                    "Need BLUETOOTH_ADMIN permission");
582            synchronized (BluetoothPbapService.this) {
583                switch (mState) {
584                    case BluetoothPbap.STATE_CONNECTED:
585                        if (mServerSession != null) {
586                            mServerSession.close();
587                            mServerSession = null;
588                        }
589                        try {
590                            closeSocket(false, true);
591                        } catch (IOException ex) {
592                            Log.e(TAG, "Caught the error: " + ex);
593                        }
594                        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
595                        break;
596                }
597            }
598        }
599    };
600
601    private final Handler mSessionStatusHandler = new Handler() {
602        @Override
603        public void handleMessage(Message msg) {
604            switch (msg.what) {
605                case START_LISTENER:
606                    if (mAdapter.isEnabled()) {
607                        startRfcommSocketListener();
608                    } else {
609                        closeService();// release all resources
610                    }
611                    break;
612                case USER_TIMEOUT:
613                    Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
614                    sendBroadcast(intent);
615                    BluetoothPbapReceiver.removePbapNotification(getApplicationContext(),
616                            BluetoothPbapReceiver.NOTIFICATION_ID_ACCESS);
617                    obexServerSessionClose();
618                    break;
619                case AUTH_TIMEOUT:
620                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
621                    sendBroadcast(i);
622                    BluetoothPbapReceiver.removePbapNotification(getApplicationContext(),
623                            BluetoothPbapReceiver.NOTIFICATION_ID_AUTH);
624                    notifyAuthCancelled();
625                    break;
626                case MSG_SERVERSESSION_CLOSE:
627                    obexServerSessionClose();
628                    mTmpTxt = getString(R.string.toast_disconnected, sRemoteDeviceName);
629                    Toast.makeText(BluetoothPbapService.this, mTmpTxt, Toast.LENGTH_SHORT).show();
630                    break;
631                case MSG_SESSION_ESTABLISHED:
632                    mTmpTxt = getString(R.string.toast_connected, sRemoteDeviceName);
633                    Toast.makeText(BluetoothPbapService.this, mTmpTxt, Toast.LENGTH_SHORT).show();
634                    break;
635                case MSG_SESSION_DISCONNECTED:
636                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
637                    break;
638                case MSG_OBEX_AUTH_CHALL:
639                    BluetoothPbapReceiver.makeNewPbapNotification(getApplicationContext(),
640                            AUTH_CHALL_ACTION);
641                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
642                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
643                    break;
644            }
645        }
646    };
647}
648