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