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