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