Home | History | Annotate | Download | only in map
      1 /*
      2 * Copyright (C) 2014 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.map;
     16 
     17 import java.io.IOException;
     18 
     19 import javax.obex.ServerSession;
     20 
     21 import android.bluetooth.BluetoothAdapter;
     22 import android.bluetooth.BluetoothDevice;
     23 import android.bluetooth.BluetoothServerSocket;
     24 import android.bluetooth.BluetoothSocket;
     25 import android.bluetooth.BluetoothUuid;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.os.Handler;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 
     32 public class BluetoothMapMasInstance {
     33     private static final String TAG = "BluetoothMapMasInstance";
     34 
     35     private static final boolean D = BluetoothMapService.DEBUG;
     36     private static final boolean V = BluetoothMapService.VERBOSE;
     37 
     38     private static final int SDP_MAP_MSG_TYPE_EMAIL    = 0x01;
     39     private static final int SDP_MAP_MSG_TYPE_SMS_GSM  = 0x02;
     40     private static final int SDP_MAP_MSG_TYPE_SMS_CDMA = 0x04;
     41     private static final int SDP_MAP_MSG_TYPE_MMS      = 0x08;
     42 
     43     private SocketAcceptThread mAcceptThread = null;
     44 
     45     private ServerSession mServerSession = null;
     46 
     47     // The handle to the socket registration with SDP
     48     private BluetoothServerSocket mServerSocket = null;
     49 
     50     // The actual incoming connection handle
     51     private BluetoothSocket mConnSocket = null;
     52 
     53     private BluetoothDevice mRemoteDevice = null; // The remote connected device
     54 
     55     private BluetoothAdapter mAdapter;
     56 
     57     private volatile boolean mInterrupted;              // Used to interrupt socket accept thread
     58 
     59     private Handler mServiceHandler = null;             // MAP service message handler
     60     private BluetoothMapService mMapService = null;     // Handle to the outer MAP service
     61     private Context mContext = null;                    // MAP service context
     62     private BluetoothMnsObexClient mMnsClient = null;   // Shared MAP MNS client
     63     private BluetoothMapEmailSettingsItem mAccount = null; //
     64     private String mBaseEmailUri = null;                // Email client base URI for this instance
     65     private int mMasInstanceId = -1;
     66     private boolean mEnableSmsMms = false;
     67     BluetoothMapContentObserver mObserver;
     68 
     69     /**
     70      * Create a e-mail MAS instance
     71      * @param callback
     72      * @param context
     73      * @param mns
     74      * @param emailBaseUri - use null to create a SMS/MMS MAS instance
     75      */
     76     public BluetoothMapMasInstance (BluetoothMapService mapService,
     77             Context context,
     78             BluetoothMapEmailSettingsItem account,
     79             int masId,
     80             boolean enableSmsMms) {
     81         mMapService = mapService;
     82         mServiceHandler = mapService.getHandler();
     83         mContext = context;
     84         mAccount = account;
     85         if(account != null) {
     86             mBaseEmailUri = account.mBase_uri;
     87         }
     88         mMasInstanceId = masId;
     89         mEnableSmsMms = enableSmsMms;
     90         init();
     91     }
     92 
     93     @Override
     94     public String toString() {
     95         return "MasId: " + mMasInstanceId + " Uri:" + mBaseEmailUri + " SMS/MMS:" + mEnableSmsMms;
     96     }
     97 
     98     private void init() {
     99         mAdapter = BluetoothAdapter.getDefaultAdapter();
    100     }
    101 
    102     public int getMasId() {
    103         return mMasInstanceId;
    104     }
    105 
    106     /**
    107      * A thread that runs in the background waiting for remote rfcomm
    108      * connect. Once a remote socket connected, this thread shall be
    109      * shutdown. When the remote disconnect, this thread shall run again
    110      * waiting for next request.
    111      */
    112     private class SocketAcceptThread extends Thread {
    113 
    114         private boolean stopped = false;
    115 
    116         @Override
    117         public void run() {
    118             BluetoothServerSocket serverSocket;
    119             if (mServerSocket == null) {
    120                 if (!initSocket()) {
    121                     return;
    122                 }
    123             }
    124 
    125             while (!stopped) {
    126                 try {
    127                     if (D) Log.d(TAG, "Accepting socket connection...");
    128                     serverSocket = mServerSocket;
    129                     if(serverSocket == null) {
    130                         Log.w(TAG, "mServerSocket is null");
    131                         break;
    132                     }
    133                     mConnSocket = serverSocket.accept();
    134                     if (D) Log.d(TAG, "Accepted socket connection...");
    135 
    136                     synchronized (BluetoothMapMasInstance.this) {
    137                         if (mConnSocket == null) {
    138                             Log.w(TAG, "mConnSocket is null");
    139                             break;
    140                         }
    141                         mRemoteDevice = mConnSocket.getRemoteDevice();
    142                     }
    143 
    144                     if (mRemoteDevice == null) {
    145                         Log.i(TAG, "getRemoteDevice() = null");
    146                         break;
    147                     }
    148 
    149                     /* Signal to the service that we have received an incoming connection.
    150                      */
    151                     boolean isValid = mMapService.onConnect(mRemoteDevice, BluetoothMapMasInstance.this);
    152 
    153                     if(isValid == false) {
    154                         // Close connection if we already have a connection with another device
    155                         Log.i(TAG, "RemoteDevice is invalid - closing.");
    156                         mConnSocket.close();
    157                         mConnSocket = null;
    158                         // now wait for a new connect
    159                     } else {
    160                         stopped = true; // job done ,close this thread;
    161                     }
    162                 } catch (IOException ex) {
    163                     stopped=true;
    164                     if (D) Log.v(TAG, "Accept exception: (expected at shutdown)", ex);
    165                 }
    166             }
    167         }
    168 
    169         void shutdown() {
    170             stopped = true;
    171             if(mServerSocket != null) {
    172                 try {
    173                     mServerSocket.close();
    174                 } catch (IOException e) {
    175                     if(D) Log.d(TAG, "Exception while thread shurdown:", e);
    176                 } finally {
    177                     mServerSocket = null;
    178                 }
    179             }
    180             interrupt();
    181         }
    182     }
    183 
    184     public void startRfcommSocketListener() {
    185         if (D) Log.d(TAG, "Map Service startRfcommSocketListener");
    186         mInterrupted = false; /* For this to work all calls to this function
    187                                  and shutdown() must be from same thread. */
    188         if (mAcceptThread == null) {
    189             mAcceptThread = new SocketAcceptThread();
    190             mAcceptThread.setName("BluetoothMapAcceptThread masId=" + mMasInstanceId);
    191             mAcceptThread.start();
    192         }
    193     }
    194 
    195     private final boolean initSocket() {
    196         if (D) Log.d(TAG, "MAS initSocket()");
    197 
    198         boolean initSocketOK = false;
    199         final int CREATE_RETRY_TIME = 10;
    200 
    201         // It's possible that create will fail in some cases. retry for 10 times
    202         for (int i = 0; (i < CREATE_RETRY_TIME) && !mInterrupted; i++) {
    203             initSocketOK = true;
    204             try {
    205                 // It is mandatory for MSE to support initiation of bonding and
    206                 // encryption.
    207                 String masId = String.format("%02x", mMasInstanceId & 0xff);
    208                 String masName = "";
    209                 int messageTypeFlags = 0;
    210                 if(mEnableSmsMms) {
    211                     masName = "SMS/MMS";
    212                     messageTypeFlags |= SDP_MAP_MSG_TYPE_SMS_GSM |
    213                                    SDP_MAP_MSG_TYPE_SMS_CDMA|
    214                                    SDP_MAP_MSG_TYPE_MMS;
    215                 }
    216                 if(mBaseEmailUri != null) {
    217                     if(mEnableSmsMms) {
    218                         masName += "/EMAIL";
    219                     } else {
    220                         masName = mAccount.getName();
    221                     }
    222                     messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL;
    223                 }
    224                 String msgTypes = String.format("%02x", messageTypeFlags & 0xff);
    225                 String sdpString = masId + msgTypes + masName;
    226                 if(V) Log.d(TAG, "  masId = " + masId +
    227                                  "\n  msgTypes = " + msgTypes +
    228                                  "\n  masName = " + masName +
    229                                  "\n  SDP string = " + sdpString);
    230                 mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord
    231                     (sdpString, BluetoothUuid.MAS.getUuid());
    232 
    233             } catch (IOException e) {
    234                 Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
    235                 initSocketOK = false;
    236             }
    237             if (!initSocketOK) {
    238                 // Need to break out of this loop if BT is being turned off.
    239                 if (mAdapter == null) break;
    240                 int state = mAdapter.getState();
    241                 if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
    242                     (state != BluetoothAdapter.STATE_ON)) {
    243                     Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
    244                     break;
    245                 }
    246                 try {
    247                     if (V) Log.v(TAG, "waiting 300 ms...");
    248                     Thread.sleep(300);
    249                 } catch (InterruptedException e) {
    250                     Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
    251                 }
    252             } else {
    253                 break;
    254             }
    255         }
    256         if (mInterrupted) {
    257             initSocketOK = false;
    258             // close server socket to avoid resource leakage
    259             closeServerSocket();
    260         }
    261 
    262         if (initSocketOK) {
    263             if (V) Log.v(TAG, "Succeed to create listening socket ");
    264 
    265         } else {
    266             Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
    267         }
    268         return initSocketOK;
    269     }
    270 
    271     /* Called for all MAS instances for each instance when auth. is completed, hence
    272      * must check if it has a valid connection before creating a session.
    273      * Returns true at success. */
    274     public boolean startObexServerSession(BluetoothMnsObexClient mnsClient) throws IOException, RemoteException {
    275         if (D) Log.d(TAG, "Map Service startObexServerSession masid = " + mMasInstanceId);
    276 
    277         if (mConnSocket != null) {
    278             if(mServerSession != null) {
    279                 // Already connected, just return true
    280                 return true;
    281             }
    282             mMnsClient = mnsClient;
    283             BluetoothMapObexServer mapServer;
    284             mObserver = new  BluetoothMapContentObserver(mContext,
    285                                                          mMnsClient,
    286                                                          this,
    287                                                          mAccount,
    288                                                          mEnableSmsMms);
    289             mObserver.init();
    290             mapServer = new BluetoothMapObexServer(mServiceHandler,
    291                                                     mContext,
    292                                                     mObserver,
    293                                                     mMasInstanceId,
    294                                                     mAccount,
    295                                                     mEnableSmsMms);
    296             // setup RFCOMM transport
    297             BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(mConnSocket);
    298             mServerSession = new ServerSession(transport, mapServer, null);
    299             if (D) Log.d(TAG, "    ServerSession started.");
    300 
    301             return true;
    302         }
    303         if (D) Log.d(TAG, "    No connection for this instance");
    304         return false;
    305     }
    306 
    307     public boolean handleSmsSendIntent(Context context, Intent intent){
    308         if(mObserver != null) {
    309             return mObserver.handleSmsSendIntent(context, intent);
    310         }
    311         return false;
    312     }
    313 
    314     /**
    315      * Check if this instance is started.
    316      * @return true if started
    317      */
    318     public boolean isStarted() {
    319         return (mConnSocket != null);
    320     }
    321 
    322     public void shutdown() {
    323         if (D) Log.d(TAG, "MAP Service shutdown");
    324 
    325         if (mServerSession != null) {
    326             mServerSession.close();
    327             mServerSession = null;
    328         }
    329         if (mObserver != null) {
    330             mObserver.deinit();
    331             mObserver = null;
    332         }
    333         mInterrupted = true;
    334         if(mAcceptThread != null) {
    335             mAcceptThread.shutdown();
    336             try {
    337                 mAcceptThread.join();
    338             } catch (InterruptedException e) {/* Not much we can do about this*/}
    339             mAcceptThread = null;
    340         }
    341 
    342         closeConnectionSocket();
    343     }
    344 
    345     /**
    346      * Stop a running server session or cleanup, and start a new
    347      * RFComm socket listener thread.
    348      */
    349     public void restartObexServerSession() {
    350         if (D) Log.d(TAG, "MAP Service stopObexServerSession");
    351 
    352         shutdown();
    353 
    354         // Last obex transaction is finished, we start to listen for incoming
    355         // connection again -
    356         startRfcommSocketListener();
    357     }
    358 
    359 
    360     private final synchronized void closeServerSocket() {
    361         // exit SocketAcceptThread early
    362         if (mServerSocket != null) {
    363             try {
    364                 // this will cause mServerSocket.accept() return early with IOException
    365                 mServerSocket.close();
    366             } catch (IOException ex) {
    367                 Log.e(TAG, "Close Server Socket error: " + ex);
    368             } finally {
    369                 mServerSocket = null;
    370             }
    371         }
    372     }
    373 
    374     private final synchronized void closeConnectionSocket() {
    375         if (mConnSocket != null) {
    376             try {
    377                 mConnSocket.close();
    378             } catch (IOException e) {
    379                 Log.e(TAG, "Close Connection Socket error: " + e.toString());
    380             } finally {
    381                 mConnSocket = null;
    382             }
    383         }
    384     }
    385 
    386 }
    387