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