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