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 android.bluetooth.BluetoothAdapter; 18 import android.bluetooth.BluetoothDevice; 19 import android.bluetooth.BluetoothSocket; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.Handler; 23 import android.os.RemoteException; 24 import android.util.Log; 25 26 import com.android.bluetooth.BluetoothObexTransport; 27 import com.android.bluetooth.IObexConnectionHandler; 28 import com.android.bluetooth.ObexServerSockets; 29 import com.android.bluetooth.map.BluetoothMapContentObserver.Msg; 30 import com.android.bluetooth.map.BluetoothMapUtils.TYPE; 31 import com.android.bluetooth.sdp.SdpManager; 32 33 import java.io.IOException; 34 import java.util.Calendar; 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.concurrent.atomic.AtomicLong; 38 39 import javax.obex.ServerSession; 40 41 public class BluetoothMapMasInstance implements IObexConnectionHandler { 42 private final String mTag; 43 private static volatile int sInstanceCounter = 0; 44 45 private static final boolean D = BluetoothMapService.DEBUG; 46 private static final boolean V = BluetoothMapService.VERBOSE; 47 48 private static final int SDP_MAP_MSG_TYPE_EMAIL = 0x01; 49 private static final int SDP_MAP_MSG_TYPE_SMS_GSM = 0x02; 50 private static final int SDP_MAP_MSG_TYPE_SMS_CDMA = 0x04; 51 private static final int SDP_MAP_MSG_TYPE_MMS = 0x08; 52 private static final int SDP_MAP_MSG_TYPE_IM = 0x10; 53 54 private static final int SDP_MAP_MAS_VERSION = 0x0102; 55 56 /* TODO: Should these be adaptive for each MAS? - e.g. read from app? */ 57 static final int SDP_MAP_MAS_FEATURES = 0x0000007F; 58 59 private ServerSession mServerSession = null; 60 // The handle to the socket registration with SDP 61 private ObexServerSockets mServerSockets = null; 62 private int mSdpHandle = -1; 63 64 // The actual incoming connection handle 65 private BluetoothSocket mConnSocket = null; 66 // The remote connected device 67 private BluetoothDevice mRemoteDevice = null; 68 private BluetoothAdapter mAdapter; 69 70 private volatile boolean mInterrupted; // Used to interrupt socket accept thread 71 private volatile boolean mShutdown = false; // Used to interrupt socket accept thread 72 private volatile boolean mAcceptNewConnections = false; 73 74 private Handler mServiceHandler = null; // MAP service message handler 75 private BluetoothMapService mMapService = null; // Handle to the outer MAP service 76 private Context mContext = null; // MAP service context 77 private BluetoothMnsObexClient mMnsClient = null; // Shared MAP MNS client 78 private BluetoothMapAccountItem mAccount = null; // 79 private String mBaseUri = null; // Client base URI for this instance 80 private int mMasInstanceId = -1; 81 private boolean mEnableSmsMms = false; 82 BluetoothMapContentObserver mObserver; 83 84 private AtomicLong mDbIndetifier = new AtomicLong(); 85 private AtomicLong mFolderVersionCounter = new AtomicLong(0); 86 private AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0); 87 private AtomicLong mImEmailConvoListVersionCounter = new AtomicLong(0); 88 89 private Map<Long, Msg> mMsgListSms = null; 90 private Map<Long, Msg> mMsgListMms = null; 91 private Map<Long, Msg> mMsgListMsg = null; 92 93 private Map<String, BluetoothMapConvoContactElement> mContactList; 94 95 private HashMap<Long, BluetoothMapConvoListingElement> mSmsMmsConvoList = 96 new HashMap<Long, BluetoothMapConvoListingElement>(); 97 98 private HashMap<Long, BluetoothMapConvoListingElement> mImEmailConvoList = 99 new HashMap<Long, BluetoothMapConvoListingElement>(); 100 101 private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK; 102 103 public static final String TYPE_SMS_MMS_STR = "SMS/MMS"; 104 public static final String TYPE_EMAIL_STR = "EMAIL"; 105 public static final String TYPE_IM_STR = "IM"; 106 107 /** 108 * Create a e-mail MAS instance 109 * @param callback 110 * @param context 111 * @param mns 112 * @param emailBaseUri - use null to create a SMS/MMS MAS instance 113 */ BluetoothMapMasInstance(BluetoothMapService mapService, Context context, BluetoothMapAccountItem account, int masId, boolean enableSmsMms)114 public BluetoothMapMasInstance(BluetoothMapService mapService, Context context, 115 BluetoothMapAccountItem account, int masId, boolean enableSmsMms) { 116 mTag = "BluetoothMapMasInstance" + sInstanceCounter++; 117 mMapService = mapService; 118 mServiceHandler = mapService.getHandler(); 119 mContext = context; 120 mAccount = account; 121 if (account != null) { 122 mBaseUri = account.mBase_uri; 123 } 124 mMasInstanceId = masId; 125 mEnableSmsMms = enableSmsMms; 126 init(); 127 } 128 removeSdpRecord()129 private void removeSdpRecord() { 130 if (mAdapter != null && mSdpHandle >= 0 && SdpManager.getDefaultManager() != null) { 131 if (V) { 132 Log.d(mTag, "Removing SDP record for MAS instance: " + mMasInstanceId 133 + " Object reference: " + this + "SDP handle: " + mSdpHandle); 134 } 135 boolean status = SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle); 136 Log.d(mTag, "RemoveSDPrecord returns " + status); 137 mSdpHandle = -1; 138 } 139 } 140 141 /* Needed only for test */ BluetoothMapMasInstance()142 protected BluetoothMapMasInstance() { 143 mTag = "BluetoothMapMasInstance" + sInstanceCounter++; 144 } 145 146 @Override toString()147 public String toString() { 148 return "MasId: " + mMasInstanceId + " Uri:" + mBaseUri + " SMS/MMS:" + mEnableSmsMms; 149 } 150 init()151 private void init() { 152 mAdapter = BluetoothAdapter.getDefaultAdapter(); 153 } 154 155 /** 156 * The data base identifier is used by connecting MCE devices to evaluate if cached data 157 * is still valid, hence only update this value when something actually invalidates the data. 158 * Situations where this must be called: 159 * - MAS ID's vs. server channels are scrambled (As neither MAS ID, name or server channels) 160 * can be used by a client to uniquely identify a specific message database - except MAS id 0 161 * we should change this value if the server channel is changed. 162 * - If a MAS instance folderVersionCounter roles over - will not happen before a long 163 * is too small to hold a unix time-stamp, hence is not handled. 164 */ updateDbIdentifier()165 private void updateDbIdentifier() { 166 mDbIndetifier.set(Calendar.getInstance().getTime().getTime()); 167 } 168 169 /** 170 * update the time stamp used for FOLDER version counter. 171 * Call once when a content provider notification caused applicable changes to the 172 * list of messages. 173 */ updateFolderVersionCounter()174 /* package */ void updateFolderVersionCounter() { 175 mFolderVersionCounter.incrementAndGet(); 176 } 177 178 /** 179 * update the CONVO LIST version counter. 180 * Call once when a content provider notification caused applicable changes to the 181 * list of contacts, or when an update is manually triggered. 182 */ updateSmsMmsConvoListVersionCounter()183 /* package */ void updateSmsMmsConvoListVersionCounter() { 184 mSmsMmsConvoListVersionCounter.incrementAndGet(); 185 } 186 updateImEmailConvoListVersionCounter()187 /* package */ void updateImEmailConvoListVersionCounter() { 188 mImEmailConvoListVersionCounter.incrementAndGet(); 189 } 190 getMsgListSms()191 /* package */ Map<Long, Msg> getMsgListSms() { 192 return mMsgListSms; 193 } 194 setMsgListSms(Map<Long, Msg> msgListSms)195 /* package */ void setMsgListSms(Map<Long, Msg> msgListSms) { 196 mMsgListSms = msgListSms; 197 } 198 getMsgListMms()199 /* package */ Map<Long, Msg> getMsgListMms() { 200 return mMsgListMms; 201 } 202 setMsgListMms(Map<Long, Msg> msgListMms)203 /* package */ void setMsgListMms(Map<Long, Msg> msgListMms) { 204 mMsgListMms = msgListMms; 205 } 206 getMsgListMsg()207 /* package */ Map<Long, Msg> getMsgListMsg() { 208 return mMsgListMsg; 209 } 210 setMsgListMsg(Map<Long, Msg> msgListMsg)211 /* package */ void setMsgListMsg(Map<Long, Msg> msgListMsg) { 212 mMsgListMsg = msgListMsg; 213 } 214 getContactList()215 /* package */ Map<String, BluetoothMapConvoContactElement> getContactList() { 216 return mContactList; 217 } 218 setContactList(Map<String, BluetoothMapConvoContactElement> contactList)219 /* package */ void setContactList(Map<String, BluetoothMapConvoContactElement> contactList) { 220 mContactList = contactList; 221 } 222 getSmsMmsConvoList()223 HashMap<Long, BluetoothMapConvoListingElement> getSmsMmsConvoList() { 224 return mSmsMmsConvoList; 225 } 226 setSmsMmsConvoList(HashMap<Long, BluetoothMapConvoListingElement> smsMmsConvoList)227 void setSmsMmsConvoList(HashMap<Long, BluetoothMapConvoListingElement> smsMmsConvoList) { 228 mSmsMmsConvoList = smsMmsConvoList; 229 } 230 getImEmailConvoList()231 HashMap<Long, BluetoothMapConvoListingElement> getImEmailConvoList() { 232 return mImEmailConvoList; 233 } 234 setImEmailConvoList(HashMap<Long, BluetoothMapConvoListingElement> imEmailConvoList)235 void setImEmailConvoList(HashMap<Long, BluetoothMapConvoListingElement> imEmailConvoList) { 236 mImEmailConvoList = imEmailConvoList; 237 } 238 239 /* package*/ getMasId()240 int getMasId() { 241 return mMasInstanceId; 242 } 243 244 /* package*/ getDbIdentifier()245 long getDbIdentifier() { 246 return mDbIndetifier.get(); 247 } 248 249 /* package*/ getFolderVersionCounter()250 long getFolderVersionCounter() { 251 return mFolderVersionCounter.get(); 252 } 253 254 /* package */ getCombinedConvoListVersionCounter()255 long getCombinedConvoListVersionCounter() { 256 long combinedVersionCounter = mSmsMmsConvoListVersionCounter.get(); 257 combinedVersionCounter += mImEmailConvoListVersionCounter.get(); 258 return combinedVersionCounter; 259 } 260 261 /** 262 * Start Obex Server Sockets and create the SDP record. 263 */ startSocketListeners()264 public synchronized void startSocketListeners() { 265 if (D) { 266 Log.d(mTag, "Map Service startSocketListeners"); 267 } 268 269 if (mServerSession != null) { 270 if (D) { 271 Log.d(mTag, "mServerSession exists - shutting it down..."); 272 } 273 mServerSession.close(); 274 mServerSession = null; 275 } 276 if (mObserver != null) { 277 if (D) { 278 Log.d(mTag, "mObserver exists - shutting it down..."); 279 } 280 mObserver.deinit(); 281 mObserver = null; 282 } 283 284 closeConnectionSocket(); 285 286 if (mServerSockets != null) { 287 mAcceptNewConnections = true; 288 } else { 289 290 mServerSockets = ObexServerSockets.create(this); 291 mAcceptNewConnections = true; 292 293 if (mServerSockets == null) { 294 // TODO: Handle - was not handled before 295 Log.e(mTag, "Failed to start the listeners"); 296 return; 297 } 298 removeSdpRecord(); 299 mSdpHandle = createMasSdpRecord(mServerSockets.getRfcommChannel(), 300 mServerSockets.getL2capPsm()); 301 // Here we might have changed crucial data, hence reset DB identifier 302 if (V) { 303 Log.d(mTag, "Creating new SDP record for MAS instance: " + mMasInstanceId 304 + " Object reference: " + this + "SDP handle: " + mSdpHandle); 305 } 306 updateDbIdentifier(); 307 } 308 } 309 310 /** 311 * Create the MAS SDP record with the information stored in the instance. 312 * @param rfcommChannel the rfcomm channel ID 313 * @param l2capPsm the l2capPsm - set to -1 to exclude 314 */ createMasSdpRecord(int rfcommChannel, int l2capPsm)315 private int createMasSdpRecord(int rfcommChannel, int l2capPsm) { 316 String masName = ""; 317 int messageTypeFlags = 0; 318 if (mEnableSmsMms) { 319 masName = TYPE_SMS_MMS_STR; 320 messageTypeFlags |= 321 SDP_MAP_MSG_TYPE_SMS_GSM | SDP_MAP_MSG_TYPE_SMS_CDMA | SDP_MAP_MSG_TYPE_MMS; 322 } 323 324 if (mBaseUri != null) { 325 if (mEnableSmsMms) { 326 if (mAccount.getType() == TYPE.EMAIL) { 327 masName += "/" + TYPE_EMAIL_STR; 328 } else if (mAccount.getType() == TYPE.IM) { 329 masName += "/" + TYPE_IM_STR; 330 } 331 } else { 332 masName = mAccount.getName(); 333 } 334 335 if (mAccount.getType() == TYPE.EMAIL) { 336 messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL; 337 } else if (mAccount.getType() == TYPE.IM) { 338 messageTypeFlags |= SDP_MAP_MSG_TYPE_IM; 339 } 340 } 341 342 return SdpManager.getDefaultManager() 343 .createMapMasRecord(masName, mMasInstanceId, rfcommChannel, l2capPsm, 344 SDP_MAP_MAS_VERSION, messageTypeFlags, SDP_MAP_MAS_FEATURES); 345 } 346 347 /* Called for all MAS instances for each instance when auth. is completed, hence 348 * must check if it has a valid connection before creating a session. 349 * Returns true at success. */ startObexServerSession(BluetoothMnsObexClient mnsClient)350 public boolean startObexServerSession(BluetoothMnsObexClient mnsClient) 351 throws IOException, RemoteException { 352 if (D) { 353 Log.d(mTag, "Map Service startObexServerSession masid = " + mMasInstanceId); 354 } 355 356 if (mConnSocket != null) { 357 if (mServerSession != null) { 358 // Already connected, just return true 359 return true; 360 } 361 362 mMnsClient = mnsClient; 363 BluetoothMapObexServer mapServer; 364 mObserver = new BluetoothMapContentObserver(mContext, mMnsClient, this, mAccount, 365 mEnableSmsMms); 366 mObserver.init(); 367 mapServer = 368 new BluetoothMapObexServer(mServiceHandler, mContext, mObserver, this, mAccount, 369 mEnableSmsMms); 370 371 // setup transport 372 BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket); 373 mServerSession = new ServerSession(transport, mapServer, null); 374 if (D) { 375 Log.d(mTag, " ServerSession started."); 376 } 377 378 return true; 379 } 380 if (D) { 381 Log.d(mTag, " No connection for this instance"); 382 } 383 return false; 384 } 385 handleSmsSendIntent(Context context, Intent intent)386 public boolean handleSmsSendIntent(Context context, Intent intent) { 387 if (mObserver != null) { 388 return mObserver.handleSmsSendIntent(context, intent); 389 } 390 return false; 391 } 392 393 /** 394 * Check if this instance is started. 395 * @return true if started 396 */ isStarted()397 public boolean isStarted() { 398 return (mConnSocket != null); 399 } 400 shutdown()401 public void shutdown() { 402 if (D) { 403 Log.d(mTag, "MAP Service shutdown"); 404 } 405 406 if (mServerSession != null) { 407 mServerSession.close(); 408 mServerSession = null; 409 } 410 if (mObserver != null) { 411 mObserver.deinit(); 412 mObserver = null; 413 } 414 415 removeSdpRecord(); 416 417 closeConnectionSocket(); 418 // Do not block for Accept thread cleanup. 419 // Fix Handler Thread block during BT Turn OFF. 420 closeServerSockets(false); 421 } 422 423 /** 424 * Signal to the ServerSockets handler that a new connection may be accepted. 425 */ restartObexServerSession()426 public void restartObexServerSession() { 427 if (D) { 428 Log.d(mTag, "MAP Service restartObexServerSession()"); 429 } 430 startSocketListeners(); 431 } 432 433 closeServerSockets(boolean block)434 private synchronized void closeServerSockets(boolean block) { 435 // exit SocketAcceptThread early 436 ObexServerSockets sockets = mServerSockets; 437 if (sockets != null) { 438 sockets.shutdown(block); 439 mServerSockets = null; 440 } 441 } 442 closeConnectionSocket()443 private synchronized void closeConnectionSocket() { 444 if (mConnSocket != null) { 445 try { 446 mConnSocket.close(); 447 } catch (IOException e) { 448 Log.e(mTag, "Close Connection Socket error: ", e); 449 } finally { 450 mConnSocket = null; 451 } 452 } 453 } 454 setRemoteFeatureMask(int supportedFeatures)455 public void setRemoteFeatureMask(int supportedFeatures) { 456 if (V) { 457 Log.v(mTag, "setRemoteFeatureMask : Curr: " + mRemoteFeatureMask); 458 } 459 mRemoteFeatureMask = supportedFeatures & SDP_MAP_MAS_FEATURES; 460 if (mObserver != null) { 461 mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask); 462 if (V) { 463 Log.v(mTag, "setRemoteFeatureMask : set: " + mRemoteFeatureMask); 464 } 465 } 466 } 467 getRemoteFeatureMask()468 public int getRemoteFeatureMask() { 469 return this.mRemoteFeatureMask; 470 } 471 472 @Override onConnect(BluetoothDevice device, BluetoothSocket socket)473 public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) { 474 if (!mAcceptNewConnections) { 475 return false; 476 } 477 /* Signal to the service that we have received an incoming connection. 478 */ 479 boolean isValid = mMapService.onConnect(device, BluetoothMapMasInstance.this); 480 481 if (isValid) { 482 mRemoteDevice = device; 483 mConnSocket = socket; 484 mAcceptNewConnections = false; 485 } 486 487 return isValid; 488 } 489 490 /** 491 * Called when an unrecoverable error occurred in an accept thread. 492 * Close down the server socket, and restart. 493 * TODO: Change to message, to call start in correct context. 494 */ 495 @Override onAcceptFailed()496 public synchronized void onAcceptFailed() { 497 mServerSockets = null; // Will cause a new to be created when calling start. 498 if (mShutdown) { 499 Log.e(mTag, "Failed to accept incomming connection - " + "shutdown"); 500 } else { 501 Log.e(mTag, "Failed to accept incomming connection - " + "restarting"); 502 startSocketListeners(); 503 } 504 } 505 506 } 507