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