BluetoothMapMasInstance.java revision 8f2c1b8e9d2b6fbc8e74cd2e6e389f00fca71f08
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 public synchronized void startRfcommSocketListener() { 261 if (D) { 262 Log.d(mTag, "Map Service startRfcommSocketListener"); 263 } 264 265 if (mServerSession != null) { 266 if (D) { 267 Log.d(mTag, "mServerSession exists - shutting it down..."); 268 } 269 mServerSession.close(); 270 mServerSession = null; 271 } 272 if (mObserver != null) { 273 if (D) { 274 Log.d(mTag, "mObserver exists - shutting it down..."); 275 } 276 mObserver.deinit(); 277 mObserver = null; 278 } 279 280 closeConnectionSocket(); 281 282 if (mServerSockets != null) { 283 mServerSockets.prepareForNewConnect(); 284 } else { 285 286 mServerSockets = ObexServerSockets.create(this); 287 288 if (mServerSockets == null) { 289 // TODO: Handle - was not handled before 290 Log.e(mTag, "Failed to start the listeners"); 291 return; 292 } 293 removeSdpRecord(); 294 mSdpHandle = createMasSdpRecord(mServerSockets.getRfcommChannel(), 295 mServerSockets.getL2capPsm()); 296 // Here we might have changed crucial data, hence reset DB identifier 297 if (V) { 298 Log.d(mTag, "Creating new SDP record for MAS instance: " + mMasInstanceId 299 + " Object reference: " + this + "SDP handle: " + mSdpHandle); 300 } 301 updateDbIdentifier(); 302 } 303 } 304 305 /** 306 * Create the MAS SDP record with the information stored in the instance. 307 * @param rfcommChannel the rfcomm channel ID 308 * @param l2capPsm the l2capPsm - set to -1 to exclude 309 */ 310 private int createMasSdpRecord(int rfcommChannel, int l2capPsm) { 311 String masName = ""; 312 int messageTypeFlags = 0; 313 if (mEnableSmsMms) { 314 masName = TYPE_SMS_MMS_STR; 315 messageTypeFlags |= 316 SDP_MAP_MSG_TYPE_SMS_GSM | SDP_MAP_MSG_TYPE_SMS_CDMA | SDP_MAP_MSG_TYPE_MMS; 317 } 318 319 if (mBaseUri != null) { 320 if (mEnableSmsMms) { 321 if (mAccount.getType() == TYPE.EMAIL) { 322 masName += "/" + TYPE_EMAIL_STR; 323 } else if (mAccount.getType() == TYPE.IM) { 324 masName += "/" + TYPE_IM_STR; 325 } 326 } else { 327 masName = mAccount.getName(); 328 } 329 330 if (mAccount.getType() == TYPE.EMAIL) { 331 messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL; 332 } else if (mAccount.getType() == TYPE.IM) { 333 messageTypeFlags |= SDP_MAP_MSG_TYPE_IM; 334 } 335 } 336 337 return SdpManager.getDefaultManager() 338 .createMapMasRecord(masName, mMasInstanceId, rfcommChannel, l2capPsm, 339 SDP_MAP_MAS_VERSION, messageTypeFlags, SDP_MAP_MAS_FEATURES); 340 } 341 342 /* Called for all MAS instances for each instance when auth. is completed, hence 343 * must check if it has a valid connection before creating a session. 344 * Returns true at success. */ 345 public boolean startObexServerSession(BluetoothMnsObexClient mnsClient) 346 throws IOException, RemoteException { 347 if (D) { 348 Log.d(mTag, "Map Service startObexServerSession masid = " + mMasInstanceId); 349 } 350 351 if (mConnSocket != null) { 352 if (mServerSession != null) { 353 // Already connected, just return true 354 return true; 355 } 356 357 mMnsClient = mnsClient; 358 BluetoothMapObexServer mapServer; 359 mObserver = new BluetoothMapContentObserver(mContext, mMnsClient, this, mAccount, 360 mEnableSmsMms); 361 mObserver.init(); 362 mapServer = 363 new BluetoothMapObexServer(mServiceHandler, mContext, mObserver, this, mAccount, 364 mEnableSmsMms); 365 366 // setup transport 367 BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket); 368 mServerSession = new ServerSession(transport, mapServer, null); 369 if (D) { 370 Log.d(mTag, " ServerSession started."); 371 } 372 373 return true; 374 } 375 if (D) { 376 Log.d(mTag, " No connection for this instance"); 377 } 378 return false; 379 } 380 381 public boolean handleSmsSendIntent(Context context, Intent intent) { 382 if (mObserver != null) { 383 return mObserver.handleSmsSendIntent(context, intent); 384 } 385 return false; 386 } 387 388 /** 389 * Check if this instance is started. 390 * @return true if started 391 */ 392 public boolean isStarted() { 393 return (mConnSocket != null); 394 } 395 396 public void shutdown() { 397 if (D) { 398 Log.d(mTag, "MAP Service shutdown"); 399 } 400 401 if (mServerSession != null) { 402 mServerSession.close(); 403 mServerSession = null; 404 } 405 if (mObserver != null) { 406 mObserver.deinit(); 407 mObserver = null; 408 } 409 410 removeSdpRecord(); 411 412 closeConnectionSocket(); 413 // Do not block for Accept thread cleanup. 414 // Fix Handler Thread block during BT Turn OFF. 415 closeServerSockets(false); 416 } 417 418 /** 419 * Signal to the ServerSockets handler that a new connection may be accepted. 420 */ 421 public void restartObexServerSession() { 422 if (D) { 423 Log.d(mTag, "MAP Service restartObexServerSession()"); 424 } 425 startRfcommSocketListener(); 426 } 427 428 429 private synchronized void closeServerSockets(boolean block) { 430 // exit SocketAcceptThread early 431 ObexServerSockets sockets = mServerSockets; 432 if (sockets != null) { 433 sockets.shutdown(block); 434 mServerSockets = null; 435 } 436 } 437 438 private synchronized void closeConnectionSocket() { 439 if (mConnSocket != null) { 440 try { 441 mConnSocket.close(); 442 } catch (IOException e) { 443 Log.e(mTag, "Close Connection Socket error: ", e); 444 } finally { 445 mConnSocket = null; 446 } 447 } 448 } 449 450 public void setRemoteFeatureMask(int supportedFeatures) { 451 if (V) { 452 Log.v(mTag, "setRemoteFeatureMask : Curr: " + mRemoteFeatureMask); 453 } 454 mRemoteFeatureMask = supportedFeatures & SDP_MAP_MAS_FEATURES; 455 if (mObserver != null) { 456 mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask); 457 if (V) { 458 Log.v(mTag, "setRemoteFeatureMask : set: " + mRemoteFeatureMask); 459 } 460 } 461 } 462 463 public int getRemoteFeatureMask() { 464 return this.mRemoteFeatureMask; 465 } 466 467 @Override 468 public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) { 469 /* Signal to the service that we have received an incoming connection. 470 */ 471 boolean isValid = mMapService.onConnect(device, BluetoothMapMasInstance.this); 472 473 if (isValid) { 474 mRemoteDevice = device; 475 mConnSocket = socket; 476 } 477 478 return isValid; 479 } 480 481 /** 482 * Called when an unrecoverable error occurred in an accept thread. 483 * Close down the server socket, and restart. 484 * TODO: Change to message, to call start in correct context. 485 */ 486 @Override 487 public synchronized void onAcceptFailed() { 488 mServerSockets = null; // Will cause a new to be created when calling start. 489 if (mShutdown) { 490 Log.e(mTag, "Failed to accept incomming connection - " + "shutdown"); 491 } else { 492 Log.e(mTag, "Failed to accept incomming connection - " + "restarting"); 493 startRfcommSocketListener(); 494 } 495 } 496 497} 498