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