Home | History | Annotate | Download | only in pbapclient
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.bluetooth.pbapclient;
     18 
     19 import android.bluetooth.BluetoothAdapter;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.BluetoothSocket;
     22 import android.os.Handler;
     23 import android.os.Handler.Callback;
     24 import android.os.HandlerThread;
     25 import android.os.Message;
     26 import android.os.Process;
     27 import android.util.Log;
     28 
     29 import java.io.IOException;
     30 import java.util.UUID;
     31 
     32 class BluetoothPbapSession implements Callback {
     33     //TODO consider cleaning file organization and naming.
     34     private static final String TAG = "com.android.bluetooth.pbapclient.BluetoothPbapSession";
     35 
     36     /* local use only */
     37     private static final int RFCOMM_CONNECTED = 1;
     38     private static final int RFCOMM_FAILED = 2;
     39 
     40     /* to BluetoothPbapClient */
     41     public static final int REQUEST_COMPLETED = 3;
     42     public static final int REQUEST_FAILED = 4;
     43     public static final int SESSION_CONNECTING = 5;
     44     public static final int SESSION_CONNECTED = 6;
     45     public static final int SESSION_DISCONNECTED = 7;
     46     public static final int AUTH_REQUESTED = 8;
     47     public static final int AUTH_TIMEOUT = 9;
     48 
     49     public static final int ACTION_LISTING = 14;
     50     public static final int ACTION_VCARD = 15;
     51     public static final int ACTION_PHONEBOOK_SIZE = 16;
     52 
     53     private static final String PBAP_UUID =
     54             "0000112f-0000-1000-8000-00805f9b34fb";
     55 
     56     private final BluetoothAdapter mAdapter;
     57     private final BluetoothDevice mDevice;
     58 
     59     private final Handler mParentHandler;
     60 
     61     private final HandlerThread mHandlerThread;
     62     private final Handler mSessionHandler;
     63 
     64     private RfcommConnectThread mConnectThread;
     65     private BluetoothPbapObexTransport mTransport;
     66 
     67     private BluetoothPbapObexSession mObexSession;
     68 
     69     private BluetoothPbapRequest mPendingRequest = null;
     70 
     71     public BluetoothPbapSession(BluetoothDevice device, Handler handler) {
     72 
     73         mAdapter = BluetoothAdapter.getDefaultAdapter();
     74         if (mAdapter == null) {
     75             throw new NullPointerException("No Bluetooth adapter in the system");
     76         }
     77 
     78         mDevice = device;
     79         mParentHandler = handler;
     80         mConnectThread = null;
     81         mTransport = null;
     82         mObexSession = null;
     83 
     84         mHandlerThread = new HandlerThread("PBAP session handler",
     85                 Process.THREAD_PRIORITY_BACKGROUND);
     86         mHandlerThread.start();
     87         mSessionHandler = new Handler(mHandlerThread.getLooper(), this);
     88     }
     89 
     90     @Override
     91     public boolean handleMessage(Message msg) {
     92         Log.d(TAG, "Handler: msg: " + msg.what);
     93 
     94         switch (msg.what) {
     95             case RFCOMM_FAILED:
     96                 mConnectThread = null;
     97 
     98                 mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
     99 
    100                 if (mPendingRequest != null) {
    101                     mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
    102                     mPendingRequest = null;
    103                 }
    104                 break;
    105 
    106             case RFCOMM_CONNECTED:
    107                 mConnectThread = null;
    108                 mTransport = (BluetoothPbapObexTransport) msg.obj;
    109                 startObexSession();
    110                 break;
    111 
    112             case BluetoothPbapObexSession.OBEX_SESSION_FAILED:
    113                 stopObexSession();
    114 
    115                 mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
    116 
    117                 if (mPendingRequest != null) {
    118                     mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
    119                     mPendingRequest = null;
    120                 }
    121                 break;
    122 
    123             case BluetoothPbapObexSession.OBEX_SESSION_CONNECTED:
    124                 mParentHandler.obtainMessage(SESSION_CONNECTED).sendToTarget();
    125 
    126                 if (mPendingRequest != null) {
    127                     mObexSession.schedule(mPendingRequest);
    128                     mPendingRequest = null;
    129                 }
    130                 break;
    131 
    132             case BluetoothPbapObexSession.OBEX_SESSION_DISCONNECTED:
    133                 mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
    134                 stopRfcomm();
    135                 break;
    136 
    137             case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_COMPLETED:
    138                 /* send to parent, process there */
    139                 mParentHandler.obtainMessage(REQUEST_COMPLETED, msg.obj).sendToTarget();
    140                 break;
    141 
    142             case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_FAILED:
    143                 /* send to parent, process there */
    144                 mParentHandler.obtainMessage(REQUEST_FAILED, msg.obj).sendToTarget();
    145                 break;
    146 
    147             case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST:
    148                 /* send to parent, process there */
    149                 mParentHandler.obtainMessage(AUTH_REQUESTED).sendToTarget();
    150 
    151                 mSessionHandler
    152                         .sendMessageDelayed(
    153                                 mSessionHandler
    154                                         .obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT),
    155                                 30000);
    156                 break;
    157 
    158             case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT:
    159                 /* stop authentication */
    160                 setAuthResponse(null);
    161 
    162                 mParentHandler.obtainMessage(AUTH_TIMEOUT).sendToTarget();
    163                 break;
    164 
    165             default:
    166                 return false;
    167         }
    168 
    169         return true;
    170     }
    171 
    172     public void start() {
    173         Log.d(TAG, "start");
    174 
    175         startRfcomm();
    176     }
    177 
    178     public void stop() {
    179         Log.d(TAG, "Stop");
    180 
    181         stopObexSession();
    182         stopRfcomm();
    183     }
    184 
    185     public void abort() {
    186         Log.d(TAG, "abort");
    187 
    188         /* fail pending request immediately */
    189         if (mPendingRequest != null) {
    190             mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
    191             mPendingRequest = null;
    192         }
    193 
    194         if (mObexSession != null) {
    195             mObexSession.abort();
    196         }
    197     }
    198 
    199     public boolean makeRequest(BluetoothPbapRequest request) {
    200         Log.v(TAG, "makeRequest: " + request.getClass().getSimpleName());
    201 
    202         if (mPendingRequest != null) {
    203             Log.w(TAG, "makeRequest: request already queued, exiting");
    204             return false;
    205         }
    206 
    207         if (mObexSession == null) {
    208             mPendingRequest = request;
    209 
    210             /*
    211              * since there is no pending request and no session it's safe to
    212              * assume that RFCOMM does not exist either and we should start
    213              * connecting it
    214              */
    215             startRfcomm();
    216 
    217             return true;
    218         }
    219 
    220         return mObexSession.schedule(request);
    221     }
    222 
    223     public boolean setAuthResponse(String key) {
    224         Log.d(TAG, "setAuthResponse key=" + key);
    225 
    226         mSessionHandler
    227                 .removeMessages(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT);
    228 
    229         /* does not make sense to set auth response when OBEX session is down */
    230         if (mObexSession == null) {
    231             return false;
    232         }
    233 
    234         return mObexSession.setAuthReply(key);
    235     }
    236 
    237     private void startRfcomm() {
    238         Log.d(TAG, "startRfcomm");
    239 
    240         if (mConnectThread == null && mObexSession == null) {
    241             mParentHandler.obtainMessage(SESSION_CONNECTING).sendToTarget();
    242 
    243             mConnectThread = new RfcommConnectThread();
    244             mConnectThread.start();
    245         }
    246 
    247         /*
    248          * don't care if mConnectThread is not null - it means RFCOMM is being
    249          * connected anyway
    250          */
    251     }
    252 
    253     private void stopRfcomm() {
    254         Log.d(TAG, "stopRfcomm");
    255 
    256         if (mConnectThread != null) {
    257             try {
    258                 // Force close the socket in case the thread is stuck doing the connect()
    259                 // call.
    260                 mConnectThread.closeSocket();
    261                 // TODO: Add timed join if closeSocket does not clean up the state.
    262                 mConnectThread.join();
    263             } catch (InterruptedException e) {
    264             }
    265 
    266             mConnectThread = null;
    267         }
    268 
    269         if (mTransport != null) {
    270             try {
    271                 mTransport.close();
    272             } catch (IOException e) {
    273             }
    274 
    275             mTransport = null;
    276         }
    277     }
    278 
    279     private void startObexSession() {
    280         Log.d(TAG, "startObexSession");
    281 
    282         mObexSession = new BluetoothPbapObexSession(mTransport);
    283         mObexSession.start(mSessionHandler);
    284     }
    285 
    286     private void stopObexSession() {
    287         Log.d(TAG, "stopObexSession");
    288 
    289         if (mObexSession != null) {
    290             mObexSession.stop();
    291             mObexSession = null;
    292         }
    293     }
    294 
    295     private class RfcommConnectThread extends Thread {
    296         private static final String TAG = "RfcommConnectThread";
    297 
    298         private BluetoothSocket mSocket;
    299 
    300         public RfcommConnectThread() {
    301             super("RfcommConnectThread");
    302         }
    303 
    304         @Override
    305         public void run() {
    306             if (mAdapter.isDiscovering()) {
    307                 mAdapter.cancelDiscovery();
    308             }
    309 
    310             try {
    311                 mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(PBAP_UUID));
    312                 mSocket.connect();
    313 
    314                 BluetoothPbapObexTransport transport;
    315                 transport = new BluetoothPbapObexTransport(mSocket);
    316 
    317                 mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
    318             } catch (IOException e) {
    319                 closeSocket();
    320                 mSessionHandler.obtainMessage(RFCOMM_FAILED).sendToTarget();
    321             }
    322 
    323         }
    324 
    325         // This method may be called from outside the thread if the connect() call above is stuck.
    326         public void closeSocket() {
    327             try {
    328                 if (mSocket != null) {
    329                     mSocket.close();
    330                 }
    331             } catch (IOException e) {
    332                 Log.e(TAG, "Error when closing socket", e);
    333             }
    334         }
    335     }
    336 }
    337