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