Home | History | Annotate | Download | only in bluetooth
      1 /*
      2 * Copyright (C) 2015 Samsung System LSI
      3 * Licensed under the Apache License, Version 2.0 (the "License");
      4 * you may not use this file except in compliance with the License.
      5 * You may obtain a copy of the License at
      6 *
      7 *      http://www.apache.org/licenses/LICENSE-2.0
      8 *
      9 * Unless required by applicable law or agreed to in writing, software
     10 * distributed under the License is distributed on an "AS IS" BASIS,
     11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 * See the License for the specific language governing permissions and
     13 * limitations under the License.
     14 */
     15 package com.android.bluetooth;
     16 
     17 import java.io.IOException;
     18 import java.util.concurrent.CountDownLatch;
     19 
     20 import javax.obex.ObexSession;
     21 import javax.obex.ResponseCodes;
     22 import javax.obex.ServerSession;
     23 
     24 import android.bluetooth.BluetoothAdapter;
     25 import android.bluetooth.BluetoothDevice;
     26 import android.bluetooth.BluetoothServerSocket;
     27 import android.bluetooth.BluetoothSocket;
     28 import android.util.Log;
     29 
     30 /**
     31  * Wraps multiple BluetoothServerSocket objects to make it possible to accept connections on
     32  * both a RFCOMM and L2CAP channel in parallel.<br>
     33  * Create an instance using {@link #create()}, which will block until the sockets have been created
     34  * and channel numbers have been assigned.<br>
     35  * Use {@link #getRfcommChannel()} and {@link #getL2capPsm()} to get the channel numbers to
     36  * put into the SDP record.<br>
     37  * Call {@link #shutdown(boolean)} to terminate the accept threads created by the call to
     38  * {@link #create(IObexConnectionHandler)}.<br>
     39  * A reference to an object of this type cannot be reused, and the {@link BluetoothServerSocket}
     40  * object references passed to this object will be closed by this object, hence cannot be reused
     41  * either (This is needed, as the only way to interrupt an accept call is to close the socket...)
     42  * <br>
     43  * When a connection is accepted,
     44  * {@link IObexConnectionHandler#onConnect(BluetoothDevice, BluetoothSocket)} will be called.<br>
     45  * If the an error occur while waiting for an incoming connection
     46  * {@link IObexConnectionHandler#onConnect(BluetoothDevice, BluetoothSocket)} will be called.<br>
     47  * In both cases the {@link ObexServerSockets} object have terminated, and a new must be created.
     48  */
     49 public class ObexServerSockets {
     50     private final String TAG;
     51     private static final String STAG = "ObexServerSockets";
     52     private static final boolean D = true; // TODO: set to false!
     53     private static final int NUMBER_OF_SOCKET_TYPES = 2; // increment if LE will be supported
     54 
     55     private final IObexConnectionHandler mConHandler;
     56     /* The wrapped sockets */
     57     private final BluetoothServerSocket mRfcommSocket;
     58     private final BluetoothServerSocket mL2capSocket;
     59     /* Handles to the accept threads. Needed for shutdown. */
     60     private SocketAcceptThread mRfcommThread = null;
     61     private SocketAcceptThread mL2capThread = null;
     62 
     63     private volatile boolean mConAccepted = false;
     64 
     65     private static volatile int sInstanceCounter = 0;
     66 
     67     private ObexServerSockets(IObexConnectionHandler conHandler,
     68             BluetoothServerSocket rfcommSocket,
     69             BluetoothServerSocket l2capSocket) {
     70         mConHandler = conHandler;
     71         mRfcommSocket = rfcommSocket;
     72         mL2capSocket = l2capSocket;
     73         TAG = "ObexServerSockets" + sInstanceCounter++;
     74     }
     75 
     76     /**
     77      * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
     78      * @param validator a reference to the {@link IObexConnectionHandler} object to call
     79      *                  to validate an incoming connection.
     80      * @return a reference to a {@link ObexServerSockets} object instance.
     81      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
     82      */
     83     public static ObexServerSockets create(IObexConnectionHandler validator) {
     84         return create(validator, BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP,
     85                 BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP);
     86     }
     87 
     88     /**
     89      * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
     90      * with specific l2cap and RFCOMM channel numbers. It is the responsibility of the caller to
     91      * ensure the numbers are free and can be used, e.g. by calling {@link #getL2capPsm()} and
     92      * {@link #getRfcommChannel()} in {@link ObexServerSockets}.
     93      * @param validator a reference to the {@link IObexConnectionHandler} object to call
     94      *                  to validate an incoming connection.
     95      * @return a reference to a {@link ObexServerSockets} object instance.
     96      * @throws IOException if it occurs while creating the {@link BluetoothServerSocket}s.
     97      *
     98      * TODO: Make public when it becomes possible to determine that the listen-call
     99      *       failed due to channel-in-use.
    100      */
    101     private static ObexServerSockets create(IObexConnectionHandler validator,
    102             int rfcommChannel, int l2capPsm) {
    103         if(D) Log.d(STAG,"create(rfcomm = " +rfcommChannel + ", l2capPsm = " + l2capPsm +")");
    104         BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
    105         if(bt == null) {
    106             throw new RuntimeException("No bluetooth adapter...");
    107         }
    108         BluetoothServerSocket rfcommSocket = null;
    109         BluetoothServerSocket l2capSocket = null;
    110         boolean initSocketOK = false;
    111         final int CREATE_RETRY_TIME = 10;
    112 
    113         // It's possible that create will fail in some cases. retry for 10 times
    114         for (int i = 0; i < CREATE_RETRY_TIME; i++) {
    115             initSocketOK = true;
    116             try {
    117                 if(rfcommSocket == null) {
    118                     rfcommSocket = bt.listenUsingRfcommOn(rfcommChannel);
    119                 }
    120                 if(l2capSocket == null) {
    121                     l2capSocket = bt.listenUsingL2capOn(l2capPsm);
    122                 }
    123             } catch (IOException e) {
    124                 Log.e(STAG, "Error create ServerSockets ",e);
    125                 initSocketOK = false;
    126             }
    127             if (!initSocketOK) {
    128                 // Need to break out of this loop if BT is being turned off.
    129                 int state = bt.getState();
    130                 if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
    131                     (state != BluetoothAdapter.STATE_ON)) {
    132                     Log.w(STAG, "initServerSockets failed as BT is (being) turned off");
    133                     break;
    134                 }
    135                 try {
    136                     if (D) Log.v(STAG, "waiting 300 ms...");
    137                     Thread.sleep(300);
    138                 } catch (InterruptedException e) {
    139                     Log.e(STAG, "create() was interrupted");
    140                 }
    141             } else {
    142                 break;
    143             }
    144         }
    145 
    146         if (initSocketOK) {
    147             if (D) Log.d(STAG, "Succeed to create listening sockets ");
    148             ObexServerSockets sockets = new ObexServerSockets(validator, rfcommSocket, l2capSocket);
    149             sockets.startAccept();
    150             return sockets;
    151         } else {
    152             Log.e(STAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
    153             return null;
    154         }
    155     }
    156 
    157     /**
    158      * Returns the channel number assigned to the RFCOMM socket. This will be a static value, that
    159      * should be reused for multiple connections.
    160      * @return the RFCOMM channel number
    161      */
    162     public int getRfcommChannel() {
    163         return mRfcommSocket.getChannel();
    164     }
    165 
    166     /**
    167      * Returns the channel number assigned to the L2CAP socket. This will be a static value, that
    168      * should be reused for multiple connections.
    169      * @return the L2CAP channel number
    170      */
    171     public int getL2capPsm() {
    172         return mL2capSocket.getChannel();
    173     }
    174 
    175     /**
    176      * Initiate the accept threads.
    177      * Will create a thread for each socket type. an incoming connection will be signaled to
    178      * the {@link IObexConnectionValidator#onConnect()}, at which point both threads will exit.
    179      */
    180     private void startAccept() {
    181         if(D) Log.d(TAG,"startAccept()");
    182         prepareForNewConnect();
    183 
    184         mRfcommThread = new SocketAcceptThread(mRfcommSocket);
    185         mRfcommThread.start();
    186 
    187         mL2capThread = new SocketAcceptThread(mL2capSocket);
    188         mL2capThread.start();
    189     }
    190 
    191     /**
    192      * Set state to accept new incoming connection. Will cause the next incoming connection to be
    193      * Signaled through {@link IObexConnectionValidator#onConnect()};
    194      */
    195     public void prepareForNewConnect() {
    196         if(D) Log.d(TAG, "prepareForNewConnect()");
    197         mConAccepted = false;
    198     }
    199 
    200     /**
    201      * Called from the AcceptThreads to signal an incoming connection.
    202      * This is the entry point that needs to synchronize between the accept
    203      * threads, and ensure only a single connection is accepted.
    204      * {@link mAcceptedSocket} is used a state variable.
    205      * @param device the connecting device.
    206      * @param conSocket the socket associated with the connection.
    207      * @return true if the connection is accepted, false otherwise.
    208      */
    209     synchronized private boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) {
    210         if(D) Log.d(TAG, "onConnect() socket: " + conSocket + " mConAccepted = " + mConAccepted);
    211         if(mConAccepted  == false && mConHandler.onConnect(device, conSocket) == true) {
    212             mConAccepted = true; // TODO: Reset this when ready to accept new connection
    213             /* Signal the remaining threads to stop.
    214             shutdown(false); */ // UPDATE: TODO: remove - redesigned to keep running...
    215             return true;
    216         }
    217         return false;
    218     }
    219 
    220     /**
    221      * Signal to the {@link IObexConnectionHandler} that an error have occurred.
    222      */
    223     synchronized private void onAcceptFailed() {
    224         Log.w(TAG,"onAcceptFailed() calling shutdown...");
    225         mConHandler.onAcceptFailed();
    226         shutdown(false);
    227     }
    228 
    229     /**
    230      * Terminate any running accept threads
    231      * @param block Set true to block the calling thread until the AcceptThreads
    232      * has ended execution
    233      */
    234     synchronized public void shutdown(boolean block) {
    235         if(D) Log.d(TAG, "shutdown(block = " + block + ")");
    236         if(mRfcommThread != null) {
    237             mRfcommThread.shutdown();
    238         }
    239         if(mL2capThread != null){
    240             mL2capThread.shutdown();
    241         }
    242         if(block == true) {
    243             while(mRfcommThread != null || mL2capThread != null) {
    244                 try {
    245                     if(mRfcommThread != null) {
    246                         mRfcommThread.join();
    247                         mRfcommThread = null;
    248                     }
    249                     if(mL2capThread != null) {
    250                         mL2capThread.join();
    251                         mL2capThread = null;
    252                     }
    253                 } catch (InterruptedException e) {
    254                     Log.i(TAG, "shutdown() interrupted, continue waiting...", e);
    255                 }
    256             }
    257         } else {
    258             mRfcommThread = null;
    259             mL2capThread = null;
    260         }
    261     }
    262 
    263     /**
    264      * A thread that runs in the background waiting for remote an incoming
    265      * connect. Once a remote socket connects, this thread will be
    266      * shutdown. When the remote disconnect, this thread shall be restarted to
    267      * accept a new connection.
    268      */
    269     private class SocketAcceptThread extends Thread {
    270 
    271         private boolean mStopped = false;
    272         private final BluetoothServerSocket mServerSocket;
    273 
    274         /**
    275          * Create a SocketAcceptThread
    276          * @param serverSocket shall never be null.
    277          * @param latch shall never be null.
    278          * @throws IllegalArgumentException
    279          */
    280         public SocketAcceptThread(BluetoothServerSocket serverSocket) {
    281             if(serverSocket == null) {
    282                 throw new IllegalArgumentException("serverSocket cannot be null");
    283             }
    284             mServerSocket = serverSocket;
    285         }
    286 
    287         /**
    288          * Run until shutdown of BT.
    289          * Accept incoming connections and reject if needed. Keep accepting incoming connections.
    290          */
    291         @Override
    292         public void run() {
    293             try {
    294                 while (!mStopped) {
    295                     BluetoothSocket connSocket;
    296                     BluetoothDevice device;
    297 
    298                     try {
    299                         if (D) Log.d(TAG, "Accepting socket connection...");
    300 
    301                         connSocket = mServerSocket.accept();
    302                         if (D) Log.d(TAG, "Accepted socket connection from: " + mServerSocket);
    303 
    304                        if (connSocket == null) {
    305                            // TODO: Do we need a max error count, to avoid spinning?
    306                             Log.w(TAG, "connSocket is null - reattempt accept");
    307                             continue;
    308                         }
    309                         device = connSocket.getRemoteDevice();
    310 
    311                         if (device == null) {
    312                             Log.i(TAG, "getRemoteDevice() = null - reattempt accept");
    313                             try{
    314                                 connSocket.close();
    315                             } catch (IOException e) {
    316                                 Log.w(TAG, "Error closing the socket. ignoring...",e );
    317                             }
    318                             continue;
    319                         }
    320 
    321                         /* Signal to the service that we have received an incoming connection.
    322                          */
    323                         boolean isValid = ObexServerSockets.this.onConnect(device, connSocket);
    324 
    325                         if(isValid == false) {
    326                             /* Close connection if we already have a connection with another device
    327                              * by responding to the OBEX connect request.
    328                              */
    329                             Log.i(TAG, "RemoteDevice is invalid - creating ObexRejectServer.");
    330                             BluetoothObexTransport obexTrans =
    331                                     new BluetoothObexTransport(connSocket);
    332                             // Create and detach a selfdestructing ServerSession to respond to any
    333                             // incoming OBEX signals.
    334                             new ServerSession(obexTrans,
    335                                     new ObexRejectServer(
    336                                             ResponseCodes.OBEX_HTTP_UNAVAILABLE,
    337                                             connSocket),
    338                                     null);
    339                             // now wait for a new connect
    340                         } else {
    341                             // now wait for a new connect
    342                         }
    343                     } catch (IOException ex) {
    344                         if(mStopped == true) {
    345                             // Expected exception because of shutdown.
    346                         } else {
    347                             Log.w(TAG, "Accept exception for " +
    348                                     mServerSocket, ex);
    349                             ObexServerSockets.this.onAcceptFailed();
    350                         }
    351                         mStopped=true;
    352                     }
    353                 } // End while()
    354             } finally {
    355                 if (D) Log.d(TAG, "AcceptThread ended for: " + mServerSocket);
    356             }
    357         }
    358 
    359         /**
    360          * Shuts down the accept threads, and closes the ServerSockets, causing all related
    361          * BluetoothSockets to disconnect, hence do not call until all all accepted connections
    362          * are ready to be disconnected.
    363          */
    364         public void shutdown() {
    365             if(mStopped == false) {
    366                 mStopped = true;
    367                 // TODO: According to the documentation, this should not close the accepted
    368                 //       sockets - and that is true, but it closes the l2cap connections, and
    369                 //       therefore it implicitly also closes the accepted sockets...
    370                 try {
    371                      mServerSocket.close();
    372                 } catch (IOException e) {
    373                     if(D) Log.d(TAG, "Exception while thread shutdown:", e);
    374                 }
    375             }
    376             // If called from another thread, interrupt the thread
    377             if(!Thread.currentThread().equals(this)){
    378                 // TODO: Will this interrupt the thread if it is blocked in synchronized?
    379                 // Else: change to use InterruptableLock
    380                 if(D) Log.d(TAG, "shutdown called from another thread - interrupt().");
    381                 interrupt();
    382             }
    383         }
    384     }
    385 
    386 }
    387