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