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 android.annotation.TargetApi; 18 import android.app.Activity; 19 import android.app.PendingIntent; 20 import android.content.BroadcastReceiver; 21 import android.content.ContentProviderClient; 22 import android.content.ContentResolver; 23 import android.content.ContentUris; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.IntentFilter.MalformedMimeTypeException; 29 import android.content.pm.PackageManager; 30 import android.database.ContentObserver; 31 import android.database.Cursor; 32 import android.net.Uri; 33 import android.os.Binder; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.ParcelFileDescriptor; 38 import android.os.Process; 39 import android.os.RemoteException; 40 import android.os.UserManager; 41 import android.provider.Telephony; 42 import android.provider.Telephony.Mms; 43 import android.provider.Telephony.MmsSms; 44 import android.provider.Telephony.Sms; 45 import android.provider.Telephony.Sms.Inbox; 46 import android.telephony.PhoneStateListener; 47 import android.telephony.ServiceState; 48 import android.telephony.SmsManager; 49 import android.telephony.SmsMessage; 50 import android.telephony.TelephonyManager; 51 import android.text.format.DateUtils; 52 import android.util.Log; 53 import android.util.Xml; 54 import android.text.TextUtils; 55 56 import org.xmlpull.v1.XmlSerializer; 57 58 import com.android.bluetooth.map.BluetoothMapUtils.TYPE; 59 import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart; 60 import com.android.bluetooth.mapapi.BluetoothMapContract; 61 import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns; 62 import com.google.android.mms.pdu.PduHeaders; 63 64 import java.io.FileNotFoundException; 65 import java.io.FileOutputStream; 66 import java.io.IOException; 67 import java.io.OutputStream; 68 import java.io.StringWriter; 69 import java.io.UnsupportedEncodingException; 70 import java.util.ArrayList; 71 import java.util.Arrays; 72 import java.util.Calendar; 73 import java.util.Collections; 74 import java.util.HashMap; 75 import java.util.HashSet; 76 import java.util.Map; 77 import java.util.Set; 78 79 import javax.obex.ResponseCodes; 80 81 @TargetApi(19) 82 public class BluetoothMapContentObserver { 83 private static final String TAG = "BluetoothMapContentObserver"; 84 85 private static final boolean D = BluetoothMapService.DEBUG; 86 private static final boolean V = BluetoothMapService.VERBOSE; 87 88 private static final String EVENT_TYPE_NEW = "NewMessage"; 89 private static final String EVENT_TYPE_DELETE = "MessageDeleted"; 90 private static final String EVENT_TYPE_REMOVED = "MessageRemoved"; 91 private static final String EVENT_TYPE_SHIFT = "MessageShift"; 92 private static final String EVENT_TYPE_DELEVERY_SUCCESS = "DeliverySuccess"; 93 private static final String EVENT_TYPE_SENDING_SUCCESS = "SendingSuccess"; 94 private static final String EVENT_TYPE_SENDING_FAILURE = "SendingFailure"; 95 private static final String EVENT_TYPE_DELIVERY_FAILURE = "DeliveryFailure"; 96 private static final String EVENT_TYPE_READ_STATUS = "ReadStatusChanged"; 97 private static final String EVENT_TYPE_CONVERSATION = "ConversationChanged"; 98 private static final String EVENT_TYPE_PRESENCE = "ParticipantPresenceChanged"; 99 private static final String EVENT_TYPE_CHAT_STATE = "ParticipantChatStateChanged"; 100 101 private static final long EVENT_FILTER_NEW_MESSAGE = 1L; 102 private static final long EVENT_FILTER_MESSAGE_DELETED = 1L<<1; 103 private static final long EVENT_FILTER_MESSAGE_SHIFT = 1L<<2; 104 private static final long EVENT_FILTER_SENDING_SUCCESS = 1L<<3; 105 private static final long EVENT_FILTER_SENDING_FAILED = 1L<<4; 106 private static final long EVENT_FILTER_DELIVERY_SUCCESS = 1L<<5; 107 private static final long EVENT_FILTER_DELIVERY_FAILED = 1L<<6; 108 private static final long EVENT_FILTER_MEMORY_FULL = 1L<<7; // Unused 109 private static final long EVENT_FILTER_MEMORY_AVAILABLE = 1L<<8; // Unused 110 private static final long EVENT_FILTER_READ_STATUS_CHANGED = 1L<<9; 111 private static final long EVENT_FILTER_CONVERSATION_CHANGED = 1L<<10; 112 private static final long EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED = 1L<<11; 113 private static final long EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED= 1L<<12; 114 private static final long EVENT_FILTER_MESSAGE_REMOVED = 1L<<13; 115 116 // TODO: If we are requesting a large message from the network, on a slow connection 117 // 20 seconds might not be enough... But then again 20 seconds is long for other 118 // cases. 119 private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS; 120 121 private Context mContext; 122 private ContentResolver mResolver; 123 private ContentProviderClient mProviderClient = null; 124 private BluetoothMnsObexClient mMnsClient; 125 private BluetoothMapMasInstance mMasInstance = null; 126 private int mMasId; 127 private boolean mEnableSmsMms = false; 128 private boolean mObserverRegistered = false; 129 private BluetoothMapAccountItem mAccount; 130 private String mAuthority = null; 131 132 // Default supported feature bit mask is 0x1f 133 private int mMapSupportedFeatures = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK; 134 // Default event report version is 1.0 135 private int mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10; 136 137 private BluetoothMapFolderElement mFolders = 138 new BluetoothMapFolderElement("DUMMY", null); // Will be set by the MAS when generated. 139 private Uri mMessageUri = null; 140 private Uri mContactUri = null; 141 142 private boolean mTransmitEvents = true; 143 144 /* To make the filter update atomic, we declare it volatile. 145 * To avoid a penalty when using it, copy the value to a local 146 * non-volatile variable when used more than once. 147 * Actually we only ever use the lower 4 bytes of this variable, 148 * hence we could manage without the volatile keyword, but as 149 * we tend to copy ways of doing things, we better do it right:-) */ 150 private volatile long mEventFilter = 0xFFFFFFFFL; 151 152 public static final int DELETED_THREAD_ID = -1; 153 154 // X-Mms-Message-Type field types. These are from PduHeaders.java 155 public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84; 156 157 // Text only MMS converted to SMS if sms parts less than or equal to defined count 158 private static final int CONVERT_MMS_TO_SMS_PART_COUNT = 10; 159 160 private TYPE mSmsType; 161 162 private static final String ACTION_MESSAGE_DELIVERY = 163 "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY"; 164 /*package*/ static final String ACTION_MESSAGE_SENT = 165 "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT"; 166 167 public static final String EXTRA_MESSAGE_SENT_HANDLE = "HANDLE"; 168 public static final String EXTRA_MESSAGE_SENT_RESULT = "result"; 169 public static final String EXTRA_MESSAGE_SENT_MSG_TYPE = "type"; 170 public static final String EXTRA_MESSAGE_SENT_URI = "uri"; 171 public static final String EXTRA_MESSAGE_SENT_RETRY = "retry"; 172 public static final String EXTRA_MESSAGE_SENT_TRANSPARENT = "transparent"; 173 public static final String EXTRA_MESSAGE_SENT_TIMESTAMP = "timestamp"; 174 175 private SmsBroadcastReceiver mSmsBroadcastReceiver = new SmsBroadcastReceiver(); 176 private CeBroadcastReceiver mCeBroadcastReceiver = new CeBroadcastReceiver(); 177 178 private boolean mStorageUnlocked = false; 179 private boolean mInitialized = false; 180 181 182 static final String[] SMS_PROJECTION = new String[] { 183 Sms._ID, 184 Sms.THREAD_ID, 185 Sms.ADDRESS, 186 Sms.BODY, 187 Sms.DATE, 188 Sms.READ, 189 Sms.TYPE, 190 Sms.STATUS, 191 Sms.LOCKED, 192 Sms.ERROR_CODE 193 }; 194 195 static final String[] SMS_PROJECTION_SHORT = new String[] { 196 Sms._ID, 197 Sms.THREAD_ID, 198 Sms.TYPE, 199 Sms.READ 200 }; 201 202 static final String[] SMS_PROJECTION_SHORT_EXT = new String[] { 203 Sms._ID, 204 Sms.THREAD_ID, 205 Sms.ADDRESS, 206 Sms.BODY, 207 Sms.DATE, 208 Sms.READ, 209 Sms.TYPE, 210 }; 211 212 static final String[] MMS_PROJECTION_SHORT = new String[] { 213 Mms._ID, 214 Mms.THREAD_ID, 215 Mms.MESSAGE_TYPE, 216 Mms.MESSAGE_BOX, 217 Mms.READ 218 }; 219 220 static final String[] MMS_PROJECTION_SHORT_EXT = new String[] { 221 Mms._ID, 222 Mms.THREAD_ID, 223 Mms.MESSAGE_TYPE, 224 Mms.MESSAGE_BOX, 225 Mms.READ, 226 Mms.DATE, 227 Mms.SUBJECT, 228 Mms.PRIORITY 229 }; 230 231 static final String[] MSG_PROJECTION_SHORT = new String[] { 232 BluetoothMapContract.MessageColumns._ID, 233 BluetoothMapContract.MessageColumns.FOLDER_ID, 234 BluetoothMapContract.MessageColumns.FLAG_READ 235 }; 236 237 static final String[] MSG_PROJECTION_SHORT_EXT = new String[] { 238 BluetoothMapContract.MessageColumns._ID, 239 BluetoothMapContract.MessageColumns.FOLDER_ID, 240 BluetoothMapContract.MessageColumns.FLAG_READ, 241 BluetoothMapContract.MessageColumns.DATE, 242 BluetoothMapContract.MessageColumns.SUBJECT, 243 BluetoothMapContract.MessageColumns.FROM_LIST, 244 BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY 245 }; 246 247 static final String[] MSG_PROJECTION_SHORT_EXT2 = new String[] { 248 BluetoothMapContract.MessageColumns._ID, 249 BluetoothMapContract.MessageColumns.FOLDER_ID, 250 BluetoothMapContract.MessageColumns.FLAG_READ, 251 BluetoothMapContract.MessageColumns.DATE, 252 BluetoothMapContract.MessageColumns.SUBJECT, 253 BluetoothMapContract.MessageColumns.FROM_LIST, 254 BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY, 255 BluetoothMapContract.MessageColumns.THREAD_ID, 256 BluetoothMapContract.MessageColumns.THREAD_NAME 257 }; 258 BluetoothMapContentObserver(final Context context, BluetoothMnsObexClient mnsClient, BluetoothMapMasInstance masInstance, BluetoothMapAccountItem account, boolean enableSmsMms)259 public BluetoothMapContentObserver(final Context context, 260 BluetoothMnsObexClient mnsClient, 261 BluetoothMapMasInstance masInstance, 262 BluetoothMapAccountItem account, 263 boolean enableSmsMms) throws RemoteException { 264 mContext = context; 265 mResolver = mContext.getContentResolver(); 266 mAccount = account; 267 mMasInstance = masInstance; 268 mMasId = mMasInstance.getMasId(); 269 270 mMapSupportedFeatures = mMasInstance.getRemoteFeatureMask(); 271 if (D) Log.d(TAG, "BluetoothMapContentObserver: Supported features " + 272 Integer.toHexString(mMapSupportedFeatures) ) ; 273 274 if((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT 275 & mMapSupportedFeatures) != 0){ 276 mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11; 277 } 278 // Make sure support for all formats result in latest version returned 279 if((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT 280 & mMapSupportedFeatures) != 0){ 281 mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12; 282 } 283 284 if(account != null) { 285 mAuthority = Uri.parse(account.mBase_uri).getAuthority(); 286 mMessageUri = Uri.parse(account.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE); 287 if (mAccount.getType() == TYPE.IM) { 288 mContactUri = Uri.parse(account.mBase_uri + "/" 289 + BluetoothMapContract.TABLE_CONVOCONTACT); 290 } 291 // TODO: We need to release this again! 292 mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority); 293 if (mProviderClient == null) { 294 throw new RemoteException("Failed to acquire provider for " + mAuthority); 295 } 296 mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT); 297 mContactList = mMasInstance.getContactList(); 298 if(mContactList == null) { 299 setContactList(new HashMap<String, BluetoothMapConvoContactElement>(), false); 300 initContactsList(); 301 } 302 } 303 mEnableSmsMms = enableSmsMms; 304 mSmsType = getSmsType(); 305 mMnsClient = mnsClient; 306 /* Get the cached list - if any, else create */ 307 mMsgListSms = mMasInstance.getMsgListSms(); 308 boolean doInit = false; 309 if(mEnableSmsMms) { 310 if(mMsgListSms == null) { 311 setMsgListSms(new HashMap<Long, Msg>(), false); 312 doInit = true; 313 } 314 mMsgListMms = mMasInstance.getMsgListMms(); 315 if(mMsgListMms == null) { 316 setMsgListMms(new HashMap<Long, Msg>(), false); 317 doInit = true; 318 } 319 } 320 if(mAccount != null) { 321 mMsgListMsg = mMasInstance.getMsgListMsg(); 322 if(mMsgListMsg == null) { 323 setMsgListMsg(new HashMap<Long, Msg>(), false); 324 doInit = true; 325 } 326 } 327 if(doInit) { 328 initMsgList(); 329 } 330 } 331 getObserverRemoteFeatureMask()332 public int getObserverRemoteFeatureMask() { 333 if (V) Log.v(TAG, "getObserverRemoteFeatureMask : " + mMapEventReportVersion 334 + " mMapSupportedFeatures: " + mMapSupportedFeatures); 335 return mMapSupportedFeatures; 336 } 337 setObserverRemoteFeatureMask(int remoteSupportedFeatures)338 public void setObserverRemoteFeatureMask(int remoteSupportedFeatures) { 339 mMapSupportedFeatures = remoteSupportedFeatures; 340 if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT 341 & mMapSupportedFeatures) != 0) { 342 mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11; 343 } 344 // Make sure support for all formats result in latest version returned 345 if ((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT 346 & mMapSupportedFeatures) != 0) { 347 mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12; 348 } 349 if (V) Log.d(TAG, "setObserverRemoteFeatureMask : " + mMapEventReportVersion 350 + " mMapSupportedFeatures : " + mMapSupportedFeatures); 351 } 352 getMsgListSms()353 private Map<Long, Msg> getMsgListSms() { 354 return mMsgListSms; 355 } 356 setMsgListSms(Map<Long, Msg> msgListSms, boolean changesDetected)357 private void setMsgListSms(Map<Long, Msg> msgListSms, boolean changesDetected) { 358 mMsgListSms = msgListSms; 359 if(changesDetected) { 360 mMasInstance.updateFolderVersionCounter(); 361 } 362 mMasInstance.setMsgListSms(msgListSms); 363 } 364 365 getMsgListMms()366 private Map<Long, Msg> getMsgListMms() { 367 return mMsgListMms; 368 } 369 370 setMsgListMms(Map<Long, Msg> msgListMms, boolean changesDetected)371 private void setMsgListMms(Map<Long, Msg> msgListMms, boolean changesDetected) { 372 mMsgListMms = msgListMms; 373 if(changesDetected) { 374 mMasInstance.updateFolderVersionCounter(); 375 } 376 mMasInstance.setMsgListMms(msgListMms); 377 } 378 379 getMsgListMsg()380 private Map<Long, Msg> getMsgListMsg() { 381 return mMsgListMsg; 382 } 383 384 setMsgListMsg(Map<Long, Msg> msgListMsg, boolean changesDetected)385 private void setMsgListMsg(Map<Long, Msg> msgListMsg, boolean changesDetected) { 386 mMsgListMsg = msgListMsg; 387 if(changesDetected) { 388 mMasInstance.updateFolderVersionCounter(); 389 } 390 mMasInstance.setMsgListMsg(msgListMsg); 391 } 392 getContactList()393 private Map<String, BluetoothMapConvoContactElement> getContactList() { 394 return mContactList; 395 } 396 397 398 /** 399 * Currently we only have data for IM / email contacts 400 * @param contactList 401 * @param changesDetected that is not chat state changed nor presence state changed. 402 */ setContactList(Map<String, BluetoothMapConvoContactElement> contactList, boolean changesDetected)403 private void setContactList(Map<String, BluetoothMapConvoContactElement> contactList, 404 boolean changesDetected) { 405 mContactList = contactList; 406 if(changesDetected) { 407 mMasInstance.updateImEmailConvoListVersionCounter(); 408 } 409 mMasInstance.setContactList(contactList); 410 } 411 sendEventNewMessage(long eventFilter)412 private static boolean sendEventNewMessage(long eventFilter) { 413 return ((eventFilter & EVENT_FILTER_NEW_MESSAGE) > 0); 414 } 415 sendEventMessageDeleted(long eventFilter)416 private static boolean sendEventMessageDeleted(long eventFilter) { 417 return ((eventFilter & EVENT_FILTER_MESSAGE_DELETED) > 0); 418 } 419 sendEventMessageShift(long eventFilter)420 private static boolean sendEventMessageShift(long eventFilter) { 421 return ((eventFilter & EVENT_FILTER_MESSAGE_SHIFT) > 0); 422 } 423 sendEventSendingSuccess(long eventFilter)424 private static boolean sendEventSendingSuccess(long eventFilter) { 425 return ((eventFilter & EVENT_FILTER_SENDING_SUCCESS) > 0); 426 } 427 sendEventSendingFailed(long eventFilter)428 private static boolean sendEventSendingFailed(long eventFilter) { 429 return ((eventFilter & EVENT_FILTER_SENDING_FAILED) > 0); 430 } 431 sendEventDeliverySuccess(long eventFilter)432 private static boolean sendEventDeliverySuccess(long eventFilter) { 433 return ((eventFilter & EVENT_FILTER_DELIVERY_SUCCESS) > 0); 434 } 435 sendEventDeliveryFailed(long eventFilter)436 private static boolean sendEventDeliveryFailed(long eventFilter) { 437 return ((eventFilter & EVENT_FILTER_DELIVERY_FAILED) > 0); 438 } 439 sendEventReadStatusChanged(long eventFilter)440 private static boolean sendEventReadStatusChanged(long eventFilter) { 441 return ((eventFilter & EVENT_FILTER_READ_STATUS_CHANGED) > 0); 442 } 443 sendEventConversationChanged(long eventFilter)444 private static boolean sendEventConversationChanged(long eventFilter) { 445 return ((eventFilter & EVENT_FILTER_CONVERSATION_CHANGED) > 0); 446 } 447 sendEventParticipantPresenceChanged(long eventFilter)448 private static boolean sendEventParticipantPresenceChanged(long eventFilter) { 449 return ((eventFilter & EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED) > 0); 450 } 451 sendEventParticipantChatstateChanged(long eventFilter)452 private static boolean sendEventParticipantChatstateChanged(long eventFilter) { 453 return ((eventFilter & EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED) > 0); 454 } 455 sendEventMessageRemoved(long eventFilter)456 private static boolean sendEventMessageRemoved(long eventFilter) { 457 return ((eventFilter & EVENT_FILTER_MESSAGE_REMOVED) > 0); 458 } 459 getSmsType()460 private TYPE getSmsType() { 461 TYPE smsType = null; 462 TelephonyManager tm = (TelephonyManager) mContext.getSystemService( 463 Context.TELEPHONY_SERVICE); 464 465 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 466 smsType = TYPE.SMS_CDMA; 467 } else { 468 smsType = TYPE.SMS_GSM; 469 } 470 471 return smsType; 472 } 473 474 private final ContentObserver mObserver = new ContentObserver(new Handler()) { 475 @Override 476 public void onChange(boolean selfChange) { 477 onChange(selfChange, null); 478 } 479 480 @Override 481 public void onChange(boolean selfChange, Uri uri) { 482 if(uri == null) { 483 Log.w(TAG, "onChange() with URI == null - not handled."); 484 return; 485 } 486 487 if (!mStorageUnlocked) { 488 Log.v(TAG, "Ignore events until storage is completely unlocked"); 489 return; 490 } 491 492 if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId() 493 + " Uri: " + uri.toString() + " selfchange: " + selfChange); 494 495 if(uri.toString().contains(BluetoothMapContract.TABLE_CONVOCONTACT)) 496 handleContactListChanges(uri); 497 else 498 handleMsgListChanges(uri); 499 } 500 }; 501 502 private static final HashMap<Integer, String> FOLDER_SMS_MAP; 503 static { 504 FOLDER_SMS_MAP = new HashMap<Integer, String>(); FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX)505 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX); FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT, BluetoothMapContract.FOLDER_NAME_SENT)506 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT, BluetoothMapContract.FOLDER_NAME_SENT); FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT, BluetoothMapContract.FOLDER_NAME_DRAFT)507 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT, BluetoothMapContract.FOLDER_NAME_DRAFT); FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX)508 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX); FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED, BluetoothMapContract.FOLDER_NAME_OUTBOX)509 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED, BluetoothMapContract.FOLDER_NAME_OUTBOX); FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED, BluetoothMapContract.FOLDER_NAME_OUTBOX)510 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED, BluetoothMapContract.FOLDER_NAME_OUTBOX); 511 } 512 getSmsFolderName(int type)513 private static String getSmsFolderName(int type) { 514 String name = FOLDER_SMS_MAP.get(type); 515 if(name != null) { 516 return name; 517 } 518 Log.e(TAG, "New SMS mailbox types have been introduced, without an update in BT..."); 519 return "Unknown"; 520 } 521 522 523 private static final HashMap<Integer, String> FOLDER_MMS_MAP; 524 static { 525 FOLDER_MMS_MAP = new HashMap<Integer, String>(); FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX)526 FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX); FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT, BluetoothMapContract.FOLDER_NAME_SENT)527 FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT, BluetoothMapContract.FOLDER_NAME_SENT); FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_DRAFTS, BluetoothMapContract.FOLDER_NAME_DRAFT)528 FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_DRAFTS, BluetoothMapContract.FOLDER_NAME_DRAFT); FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX)529 FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX); 530 } 531 getMmsFolderName(int mailbox)532 private static String getMmsFolderName(int mailbox) { 533 String name = FOLDER_MMS_MAP.get(mailbox); 534 if(name != null) { 535 return name; 536 } 537 Log.e(TAG, "New MMS mailboxes have been introduced, without an update in BT..."); 538 return "Unknown"; 539 } 540 541 /** 542 * Set the folder structure to be used for this instance. 543 * @param folderStructure 544 */ setFolderStructure(BluetoothMapFolderElement folderStructure)545 public void setFolderStructure(BluetoothMapFolderElement folderStructure) { 546 this.mFolders = folderStructure; 547 } 548 549 private class ConvoContactInfo { 550 public int mConvoColConvoId = -1; 551 public int mConvoColLastActivity = -1; 552 public int mConvoColName = -1; 553 // public int mConvoColRead = -1; 554 // public int mConvoColVersionCounter = -1; 555 public int mContactColUci = -1; 556 public int mContactColConvoId = -1; 557 public int mContactColName = -1; 558 public int mContactColNickname = -1; 559 public int mContactColBtUid = -1; 560 public int mContactColChatState = -1; 561 public int mContactColContactId = -1; 562 public int mContactColLastActive = -1; 563 public int mContactColPresenceState = -1; 564 public int mContactColPresenceText = -1; 565 public int mContactColPriority = -1; 566 public int mContactColLastOnline = -1; 567 setConvoColunms(Cursor c)568 public void setConvoColunms(Cursor c) { 569 // mConvoColConvoId = c.getColumnIndex( 570 // BluetoothMapContract.ConversationColumns.THREAD_ID); 571 // mConvoColLastActivity = c.getColumnIndex( 572 // BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY); 573 // mConvoColName = c.getColumnIndex( 574 // BluetoothMapContract.ConversationColumns.THREAD_NAME); 575 mContactColConvoId = c.getColumnIndex( 576 BluetoothMapContract.ConvoContactColumns.CONVO_ID); 577 mContactColName = c.getColumnIndex( 578 BluetoothMapContract.ConvoContactColumns.NAME); 579 mContactColNickname = c.getColumnIndex( 580 BluetoothMapContract.ConvoContactColumns.NICKNAME); 581 mContactColBtUid = c.getColumnIndex( 582 BluetoothMapContract.ConvoContactColumns.X_BT_UID); 583 mContactColChatState = c.getColumnIndex( 584 BluetoothMapContract.ConvoContactColumns.CHAT_STATE); 585 mContactColUci = c.getColumnIndex( 586 BluetoothMapContract.ConvoContactColumns.UCI); 587 mContactColNickname = c.getColumnIndex( 588 BluetoothMapContract.ConvoContactColumns.NICKNAME); 589 mContactColLastActive = c.getColumnIndex( 590 BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE); 591 mContactColName = c.getColumnIndex( 592 BluetoothMapContract.ConvoContactColumns.NAME); 593 mContactColPresenceState = c.getColumnIndex( 594 BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE); 595 mContactColPresenceText = c.getColumnIndex( 596 BluetoothMapContract.ConvoContactColumns.STATUS_TEXT); 597 mContactColPriority = c.getColumnIndex( 598 BluetoothMapContract.ConvoContactColumns.PRIORITY); 599 mContactColLastOnline = c.getColumnIndex( 600 BluetoothMapContract.ConvoContactColumns.LAST_ONLINE); 601 } 602 } 603 604 private class Event { 605 String eventType; 606 long handle; 607 String folder = null; 608 String oldFolder = null; 609 TYPE msgType; 610 /* Extended event parameters in MAP Event version 1.1 */ 611 String datetime = null; // OBEX time "YYYYMMDDTHHMMSS" 612 String uci = null; 613 String subject = null; 614 String senderName = null; 615 String priority = null; 616 /* Event parameters in MAP Event version 1.2 */ 617 String conversationName = null; 618 long conversationID = -1; 619 int presenceState = BluetoothMapContract.PresenceState.UNKNOWN; 620 String presenceStatus = null; 621 int chatState = BluetoothMapContract.ChatState.UNKNOWN; 622 623 final static String PATH = "telecom/msg/"; 624 setFolderPath(String name, TYPE type)625 private void setFolderPath(String name, TYPE type) { 626 if (name != null) { 627 if(type == TYPE.EMAIL || type == TYPE.IM) { 628 this.folder = name; 629 } else { 630 this.folder = PATH + name; 631 } 632 } else { 633 this.folder = null; 634 } 635 } 636 Event(String eventType, long handle, String folder, String oldFolder, TYPE msgType)637 public Event(String eventType, long handle, String folder, 638 String oldFolder, TYPE msgType) { 639 this.eventType = eventType; 640 this.handle = handle; 641 setFolderPath(folder, msgType); 642 if (oldFolder != null) { 643 if(msgType == TYPE.EMAIL || msgType == TYPE.IM) { 644 this.oldFolder = oldFolder; 645 } else { 646 this.oldFolder = PATH + oldFolder; 647 } 648 } else { 649 this.oldFolder = null; 650 } 651 this.msgType = msgType; 652 } 653 Event(String eventType, long handle, String folder, TYPE msgType)654 public Event(String eventType, long handle, String folder, TYPE msgType) { 655 this.eventType = eventType; 656 this.handle = handle; 657 setFolderPath(folder, msgType); 658 this.msgType = msgType; 659 } 660 661 /* extended event type 1.1 */ Event(String eventType, long handle, String folder, TYPE msgType, String datetime, String subject, String senderName, String priority)662 public Event(String eventType, long handle, String folder, TYPE msgType, 663 String datetime, String subject, String senderName, String priority) { 664 this.eventType = eventType; 665 this.handle = handle; 666 setFolderPath(folder, msgType); 667 this.msgType = msgType; 668 this.datetime = datetime; 669 if (subject != null) { 670 this.subject = BluetoothMapUtils.stripInvalidChars(subject); 671 } 672 if (senderName != null) { 673 this.senderName = BluetoothMapUtils.stripInvalidChars(senderName); 674 } 675 this.priority = priority; 676 } 677 678 /* extended event type 1.2 message events */ Event(String eventType, long handle, String folder, TYPE msgType, String datetime, String subject, String senderName, String priority, long conversationID, String conversationName)679 public Event(String eventType, long handle, String folder, TYPE msgType, 680 String datetime, String subject, String senderName, String priority, 681 long conversationID, String conversationName) { 682 this.eventType = eventType; 683 this.handle = handle; 684 setFolderPath(folder, msgType); 685 this.msgType = msgType; 686 this.datetime = datetime; 687 if (subject != null) { 688 this.subject = BluetoothMapUtils.stripInvalidChars(subject); 689 } 690 if (senderName != null) { 691 this.senderName = BluetoothMapUtils.stripInvalidChars(senderName); 692 } 693 if (conversationID != 0) { 694 this.conversationID = conversationID; 695 } 696 if (conversationName != null) { 697 this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName); 698 } 699 this.priority = priority; 700 } 701 702 /* extended event type 1.2 for conversation, presence or chat state changed events */ Event(String eventType, String uci, TYPE msgType, String name, String priority, String lastActivity, long conversationID, String conversationName, int presenceState, String presenceStatus, int chatState)703 public Event(String eventType, String uci, TYPE msgType, String name, String priority, 704 String lastActivity, long conversationID, String conversationName, 705 int presenceState, String presenceStatus, int chatState) { 706 this.eventType = eventType; 707 this.uci = uci; 708 this.msgType = msgType; 709 if (name != null) { 710 this.senderName = BluetoothMapUtils.stripInvalidChars(name); 711 } 712 this.priority = priority; 713 this.datetime = lastActivity; 714 if (conversationID != 0) { 715 this.conversationID = conversationID; 716 } 717 if (conversationName != null) { 718 this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName); 719 } 720 if (presenceState != BluetoothMapContract.PresenceState.UNKNOWN) { 721 this.presenceState = presenceState; 722 } 723 if (presenceStatus != null) { 724 this.presenceStatus = BluetoothMapUtils.stripInvalidChars(presenceStatus); 725 } 726 if (chatState != BluetoothMapContract.ChatState.UNKNOWN) { 727 this.chatState = chatState; 728 } 729 } 730 encode()731 public byte[] encode() throws UnsupportedEncodingException { 732 StringWriter sw = new StringWriter(); 733 XmlSerializer xmlEvtReport = Xml.newSerializer(); 734 735 try { 736 xmlEvtReport.setOutput(sw); 737 xmlEvtReport.startDocument("UTF-8", true); 738 xmlEvtReport.text("\r\n"); 739 xmlEvtReport.startTag("", "MAP-event-report"); 740 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 741 xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V10_STR); 742 } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 743 xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V11_STR); 744 } else { 745 xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V12_STR); 746 } 747 xmlEvtReport.startTag("", "event"); 748 xmlEvtReport.attribute("", "type", eventType); 749 if (eventType.equals(EVENT_TYPE_CONVERSATION) || 750 eventType.equals(EVENT_TYPE_PRESENCE) || 751 eventType.equals(EVENT_TYPE_CHAT_STATE)) { 752 xmlEvtReport.attribute("", "participant_uci", uci); 753 } else { 754 xmlEvtReport.attribute("", "handle", 755 BluetoothMapUtils.getMapHandle(handle, msgType)); 756 } 757 758 if (folder != null) { 759 xmlEvtReport.attribute("", "folder", folder); 760 } 761 if (oldFolder != null) { 762 xmlEvtReport.attribute("", "old_folder", oldFolder); 763 } 764 /* Avoid possible NPE for "msgType" "null" value. "msgType" 765 * is a implied attribute and will be set "null" for events 766 * like "memory full" or "memory available" */ 767 if (msgType != null) { 768 xmlEvtReport.attribute("", "msg_type", msgType.name()); 769 } 770 /* If MAP event report version is above 1.0 send 771 * extended event report parameters */ 772 if (datetime != null) { 773 xmlEvtReport.attribute("", "datetime", datetime); 774 } 775 if (subject != null) { 776 xmlEvtReport.attribute("", "subject", 777 subject.substring(0,subject.length() < 256 ? subject.length() : 256)); 778 } 779 if (senderName != null) { 780 xmlEvtReport.attribute("", "sender_name", senderName); 781 } 782 if (priority != null) { 783 xmlEvtReport.attribute("", "priority", priority); 784 } 785 786 //} 787 /* Include conversation information from event version 1.2 */ 788 if (mMapEventReportVersion > BluetoothMapUtils.MAP_EVENT_REPORT_V11 ) { 789 if (conversationName != null) { 790 xmlEvtReport.attribute("", "conversation_name", conversationName); 791 } 792 if (conversationID != -1) { 793 // Convert provider conversation handle to string incl type 794 xmlEvtReport.attribute("", "conversation_id", 795 BluetoothMapUtils.getMapConvoHandle(conversationID, msgType)); 796 } 797 if (eventType.equals(EVENT_TYPE_PRESENCE)) { 798 if (presenceState != 0) { 799 // Convert provider conversation handle to string incl type 800 xmlEvtReport.attribute("", "presence_availability", 801 String.valueOf(presenceState)); 802 } 803 if (presenceStatus != null) { 804 // Convert provider conversation handle to string incl type 805 xmlEvtReport.attribute("", "presence_status", 806 presenceStatus.substring( 807 0,presenceStatus.length() < 256 ? subject.length() : 256)); 808 } 809 } 810 if (eventType.equals(EVENT_TYPE_PRESENCE)) { 811 if (chatState != 0) { 812 // Convert provider conversation handle to string incl type 813 xmlEvtReport.attribute("", "chat_state", String.valueOf(chatState)); 814 } 815 } 816 817 } 818 xmlEvtReport.endTag("", "event"); 819 xmlEvtReport.endTag("", "MAP-event-report"); 820 xmlEvtReport.endDocument(); 821 } catch (IllegalArgumentException e) { 822 if(D) Log.w(TAG,e); 823 } catch (IllegalStateException e) { 824 if(D) Log.w(TAG,e); 825 } catch (IOException e) { 826 if(D) Log.w(TAG,e); 827 } 828 829 if (V) Log.d(TAG, sw.toString()); 830 831 return sw.toString().getBytes("UTF-8"); 832 } 833 } 834 835 /*package*/ class Msg { 836 long id; 837 int type; // Used as folder for SMS/MMS 838 int threadId; // Used for SMS/MMS at delete 839 long folderId = -1; // Email folder ID 840 long oldFolderId = -1; // Used for email undelete 841 boolean localInitiatedSend = false; // Used for MMS to filter out events 842 boolean transparent = false; // Used for EMAIL to delete message sent with transparency 843 int flagRead = -1; // Message status read/unread 844 Msg(long id, int type, int threadId, int readFlag)845 public Msg(long id, int type, int threadId, int readFlag) { 846 this.id = id; 847 this.type = type; 848 this.threadId = threadId; 849 this.flagRead = readFlag; 850 } Msg(long id, long folderId, int readFlag)851 public Msg(long id, long folderId, int readFlag) { 852 this.id = id; 853 this.folderId = folderId; 854 this.flagRead = readFlag; 855 } 856 857 /* Eclipse generated hashCode() and equals() to make 858 * hashMap lookup work independent of whether the obj 859 * is used for email or SMS/MMS and whether or not the 860 * oldFolder is set. */ 861 @Override hashCode()862 public int hashCode() { 863 final int prime = 31; 864 int result = 1; 865 result = prime * result + (int) (id ^ (id >>> 32)); 866 return result; 867 } 868 869 @Override equals(Object obj)870 public boolean equals(Object obj) { 871 if (this == obj) 872 return true; 873 if (obj == null) 874 return false; 875 if (getClass() != obj.getClass()) 876 return false; 877 Msg other = (Msg) obj; 878 if (id != other.id) 879 return false; 880 return true; 881 } 882 } 883 884 private Map<Long, Msg> mMsgListSms = null; 885 886 private Map<Long, Msg> mMsgListMms = null; 887 888 private Map<Long, Msg> mMsgListMsg = null; 889 890 private Map<String, BluetoothMapConvoContactElement> mContactList = null; 891 setNotificationRegistration(int notificationStatus)892 public int setNotificationRegistration(int notificationStatus) throws RemoteException { 893 // Forward the request to the MNS thread as a message - including the MAS instance ID. 894 if(D) Log.d(TAG,"setNotificationRegistration() enter"); 895 if (mMnsClient == null) { 896 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 897 } 898 Handler mns = mMnsClient.getMessageHandler(); 899 if (mns != null) { 900 Message msg = mns.obtainMessage(); 901 if (mMnsClient.isValidMnsRecord()) { 902 msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION; 903 } else { 904 //Trigger SDP Search and notificaiton registration , if SDP record not found. 905 msg.what = BluetoothMnsObexClient.MSG_MNS_SDP_SEARCH_REGISTRATION; 906 if (mMnsClient.mMnsLstRegRqst != null && 907 (mMnsClient.mMnsLstRegRqst.isSearchInProgress())) { 908 /* 1. Disallow next Notification ON Request : 909 * - Respond "Service Unavailable" as SDP Search and last notification 910 * registration ON request is already InProgress. 911 * - Next notification ON Request will be allowed ONLY after search 912 * and connect for last saved request [Replied with OK ] is processed. 913 */ 914 if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) { 915 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 916 } else { 917 /* 2. Allow next Notification OFF Request: 918 * - Keep the SDP search still in progress. 919 * - Disconnect and Deregister the contentObserver. 920 */ 921 msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION; 922 } 923 } 924 } 925 msg.arg1 = mMasId; 926 msg.arg2 = notificationStatus; 927 mns.sendMessageDelayed(msg, 10); // Send message without forcing a context switch 928 /* Some devices - e.g. PTS needs to get the unregister confirm before we actually 929 * disconnect the MNS. */ 930 if(D) Log.d(TAG,"setNotificationRegistration() send : " + msg.what + " to MNS "); 931 return ResponseCodes.OBEX_HTTP_OK; 932 } else { 933 // This should not happen except at shutdown. 934 if(D) Log.d(TAG,"setNotificationRegistration() Unable to send registration request"); 935 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 936 } 937 } 938 eventMaskContainsContacts(long mask)939 boolean eventMaskContainsContacts(long mask) { 940 return sendEventParticipantPresenceChanged(mask); 941 } 942 eventMaskContainsCovo(long mask)943 boolean eventMaskContainsCovo(long mask) { 944 return (sendEventConversationChanged(mask) 945 || sendEventParticipantChatstateChanged(mask)); 946 } 947 948 /* Overwrite the existing notification filter. Will register/deregister observers for 949 * the Contacts and Conversation table as needed. We keep the message observer 950 * at all times. */ setNotificationFilter(long newFilter)951 /*package*/ synchronized void setNotificationFilter(long newFilter) { 952 long oldFilter = mEventFilter; 953 mEventFilter = newFilter; 954 /* Contacts */ 955 if(!eventMaskContainsContacts(oldFilter) && 956 eventMaskContainsContacts(newFilter)) { 957 // TODO: 958 // Enable the observer 959 // Reset the contacts list 960 } 961 /* Conversations */ 962 if(!eventMaskContainsCovo(oldFilter) && 963 eventMaskContainsCovo(newFilter)) { 964 // TODO: 965 // Enable the observer 966 // Reset the conversations list 967 } 968 } 969 registerObserver()970 public void registerObserver() throws RemoteException{ 971 if (V) Log.d(TAG, "registerObserver"); 972 973 if (mObserverRegistered) 974 return; 975 976 if(mAccount != null) { 977 978 mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority); 979 if (mProviderClient == null) { 980 throw new RemoteException("Failed to acquire provider for " + mAuthority); 981 } 982 mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT); 983 984 // If there is a change in the database before we init the lists we will be sending 985 // loads of events - hence init before register. 986 if(mAccount.getType() == TYPE.IM) { 987 // Further add contact list tracking 988 initContactsList(); 989 } 990 } 991 // If there is a change in the database before we init the lists we will be sending 992 // loads of events - hence init before register. 993 initMsgList(); 994 995 /* Use MmsSms Uri since the Sms Uri is not notified on deletes */ 996 if(mEnableSmsMms){ 997 //this is sms/mms 998 mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver); 999 mObserverRegistered = true; 1000 } 1001 1002 if(mAccount != null) { 1003 /* For URI's without account ID */ 1004 Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/" 1005 + BluetoothMapContract.TABLE_MESSAGE); 1006 if(D) Log.d(TAG, "Registering observer for: " + uri); 1007 mResolver.registerContentObserver(uri, true, mObserver); 1008 1009 /* For URI's with account ID - is handled the same way as without ID, but is 1010 * only triggered for MAS instances with matching account ID. */ 1011 uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE); 1012 if(D) Log.d(TAG, "Registering observer for: " + uri); 1013 mResolver.registerContentObserver(uri, true, mObserver); 1014 1015 if(mAccount.getType() == TYPE.IM) { 1016 1017 uri = Uri.parse(mAccount.mBase_uri_no_account + "/" 1018 + BluetoothMapContract.TABLE_CONVOCONTACT); 1019 if(D) Log.d(TAG, "Registering observer for: " + uri); 1020 mResolver.registerContentObserver(uri, true, mObserver); 1021 1022 /* For URI's with account ID - is handled the same way as without ID, but is 1023 * only triggered for MAS instances with matching account ID. */ 1024 uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_CONVOCONTACT); 1025 if(D) Log.d(TAG, "Registering observer for: " + uri); 1026 mResolver.registerContentObserver(uri, true, mObserver); 1027 } 1028 1029 mObserverRegistered = true; 1030 } 1031 } 1032 unregisterObserver()1033 public void unregisterObserver() { 1034 if (V) Log.d(TAG, "unregisterObserver"); 1035 mResolver.unregisterContentObserver(mObserver); 1036 mObserverRegistered = false; 1037 if(mProviderClient != null){ 1038 mProviderClient.release(); 1039 mProviderClient = null; 1040 } 1041 } 1042 1043 /** 1044 * Per design it is only possible to call the refreshXxxx functions sequentially, hence it 1045 * is safe to modify mTransmitEvents without synchronization. 1046 */ refreshFolderVersionCounter()1047 /* package */ void refreshFolderVersionCounter() { 1048 if (mObserverRegistered) { 1049 // As we have observers, we already keep the counter up-to-date. 1050 return; 1051 } 1052 /* We need to perform the same functionality, as when we receive a notification change, 1053 hence we: 1054 - disable the event transmission 1055 - triggers the code for updates 1056 - enable the event transmission */ 1057 mTransmitEvents = false; 1058 try { 1059 if(mEnableSmsMms) { 1060 handleMsgListChangesSms(); 1061 handleMsgListChangesMms(); 1062 } 1063 if(mAccount != null) { 1064 try { 1065 handleMsgListChangesMsg(mMessageUri); 1066 } catch (RemoteException e) { 1067 Log.e(TAG, "Unable to update FolderVersionCounter. - Not fatal, but can cause" + 1068 " undesirable user experience!", e); 1069 } 1070 } 1071 } finally { 1072 // Ensure we always enable events again 1073 mTransmitEvents = true; 1074 } 1075 } 1076 refreshConvoListVersionCounter()1077 /* package */ void refreshConvoListVersionCounter() { 1078 if (mObserverRegistered) { 1079 // As we have observers, we already keep the counter up-to-date. 1080 return; 1081 } 1082 /* We need to perform the same functionality, as when we receive a notification change, 1083 hence we: 1084 - disable event transmission 1085 - triggers the code for updates 1086 - enable event transmission */ 1087 mTransmitEvents = false; 1088 try { 1089 if((mAccount != null) && (mContactUri != null)) { 1090 handleContactListChanges(mContactUri); 1091 } 1092 } finally { 1093 // Ensure we always enable events again 1094 mTransmitEvents = true; 1095 } 1096 } 1097 sendEvent(Event evt)1098 private void sendEvent(Event evt) { 1099 1100 if(mTransmitEvents == false) { 1101 if(V) Log.v(TAG, "mTransmitEvents == false - don't send event."); 1102 return; 1103 } 1104 1105 if(D)Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " " + evt.folder + " " 1106 + evt.oldFolder + " " + evt.msgType.name() + " " + evt.datetime + " " 1107 + evt.subject + " " + evt.senderName + " " + evt.priority ); 1108 1109 if (mMnsClient == null || mMnsClient.isConnected() == false) { 1110 Log.d(TAG, "sendEvent: No MNS client registered or connected- don't send event"); 1111 return; 1112 } 1113 1114 /* Enable use of the cache for checking the filter */ 1115 long eventFilter = mEventFilter; 1116 1117 /* This should have been a switch on the string, but it is not allowed in Java 1.6 */ 1118 /* WARNING: Here we do pointer compare for the string to speed up things, that is. 1119 * HENCE: always use the EVENT_TYPE_"defines" */ 1120 if(evt.eventType == EVENT_TYPE_NEW) { 1121 if(!sendEventNewMessage(eventFilter)) { 1122 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1123 return; 1124 } 1125 } else if(evt.eventType == EVENT_TYPE_DELETE) { 1126 if(!sendEventMessageDeleted(eventFilter)) { 1127 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1128 return; 1129 } 1130 } else if(evt.eventType == EVENT_TYPE_REMOVED) { 1131 if(!sendEventMessageRemoved(eventFilter)) { 1132 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1133 return; 1134 } 1135 } else if(evt.eventType == EVENT_TYPE_SHIFT) { 1136 if(!sendEventMessageShift(eventFilter)) { 1137 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1138 return; 1139 } 1140 } else if(evt.eventType == EVENT_TYPE_DELEVERY_SUCCESS) { 1141 if(!sendEventDeliverySuccess(eventFilter)) { 1142 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1143 return; 1144 } 1145 } else if(evt.eventType == EVENT_TYPE_SENDING_SUCCESS) { 1146 if(!sendEventSendingSuccess(eventFilter)) { 1147 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1148 return; 1149 } 1150 } else if(evt.eventType == EVENT_TYPE_SENDING_FAILURE) { 1151 if(!sendEventSendingFailed(eventFilter)) { 1152 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1153 return; 1154 } 1155 } else if(evt.eventType == EVENT_TYPE_DELIVERY_FAILURE) { 1156 if(!sendEventDeliveryFailed(eventFilter)) { 1157 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1158 return; 1159 } 1160 } else if(evt.eventType == EVENT_TYPE_READ_STATUS) { 1161 if(!sendEventReadStatusChanged(eventFilter)) { 1162 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1163 return; 1164 } 1165 } else if(evt.eventType == EVENT_TYPE_CONVERSATION) { 1166 if(!sendEventConversationChanged(eventFilter)) { 1167 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1168 return; 1169 } 1170 } else if(evt.eventType == EVENT_TYPE_PRESENCE) { 1171 if(!sendEventParticipantPresenceChanged(eventFilter)) { 1172 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1173 return; 1174 } 1175 } else if(evt.eventType == EVENT_TYPE_CHAT_STATE) { 1176 if(!sendEventParticipantChatstateChanged(eventFilter)) { 1177 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1178 return; 1179 } 1180 } 1181 1182 try { 1183 mMnsClient.sendEvent(evt.encode(), mMasId); 1184 } catch (UnsupportedEncodingException ex) { 1185 /* do nothing */ 1186 if (D) Log.e(TAG, "Exception - should not happen: ",ex); 1187 } 1188 } 1189 initMsgList()1190 private void initMsgList() throws RemoteException { 1191 if (V) Log.d(TAG, "initMsgList"); 1192 UserManager manager = UserManager.get(mContext); 1193 if (manager == null || manager.isUserUnlocked()) return; 1194 1195 if (mEnableSmsMms) { 1196 HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>(); 1197 1198 Cursor c = mResolver.query(Sms.CONTENT_URI, 1199 SMS_PROJECTION_SHORT, null, null, null); 1200 try { 1201 if (c != null && c.moveToFirst()) { 1202 do { 1203 long id = c.getLong(c.getColumnIndex(Sms._ID)); 1204 int type = c.getInt(c.getColumnIndex(Sms.TYPE)); 1205 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 1206 int read = c.getInt(c.getColumnIndex(Sms.READ)); 1207 1208 Msg msg = new Msg(id, type, threadId, read); 1209 msgListSms.put(id, msg); 1210 } while (c.moveToNext()); 1211 } 1212 } finally { 1213 if (c != null) c.close(); 1214 } 1215 1216 synchronized(getMsgListSms()) { 1217 getMsgListSms().clear(); 1218 setMsgListSms(msgListSms, true); // Set initial folder version counter 1219 } 1220 1221 HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>(); 1222 1223 c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null); 1224 try { 1225 if (c != null && c.moveToFirst()) { 1226 do { 1227 long id = c.getLong(c.getColumnIndex(Mms._ID)); 1228 int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 1229 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 1230 int read = c.getInt(c.getColumnIndex(Mms.READ)); 1231 1232 Msg msg = new Msg(id, type, threadId, read); 1233 msgListMms.put(id, msg); 1234 } while (c.moveToNext()); 1235 } 1236 } finally { 1237 if (c != null) c.close(); 1238 } 1239 1240 synchronized(getMsgListMms()) { 1241 getMsgListMms().clear(); 1242 setMsgListMms(msgListMms, true); // Set initial folder version counter 1243 } 1244 } 1245 1246 if(mAccount != null) { 1247 HashMap<Long, Msg> msgList = new HashMap<Long, Msg>(); 1248 Uri uri = mMessageUri; 1249 Cursor c = mProviderClient.query(uri, MSG_PROJECTION_SHORT, null, null, null); 1250 1251 try { 1252 if (c != null && c.moveToFirst()) { 1253 do { 1254 long id = c.getLong(c.getColumnIndex(MessageColumns._ID)); 1255 long folderId = c.getInt(c.getColumnIndex( 1256 BluetoothMapContract.MessageColumns.FOLDER_ID)); 1257 int readFlag = c.getInt(c.getColumnIndex( 1258 BluetoothMapContract.MessageColumns.FLAG_READ)); 1259 Msg msg = new Msg(id, folderId, readFlag); 1260 msgList.put(id, msg); 1261 } while (c.moveToNext()); 1262 } 1263 } finally { 1264 if (c != null) c.close(); 1265 } 1266 1267 synchronized(getMsgListMsg()) { 1268 getMsgListMsg().clear(); 1269 setMsgListMsg(msgList, true); 1270 } 1271 } 1272 } 1273 initContactsList()1274 private void initContactsList() throws RemoteException { 1275 if (V) Log.d(TAG, "initContactsList"); 1276 if(mContactUri == null) { 1277 if (D) Log.d(TAG, "initContactsList() no mContactUri - nothing to init"); 1278 return; 1279 } 1280 Uri uri = mContactUri; 1281 Cursor c = mProviderClient.query(uri, 1282 BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION, 1283 null, null, null); 1284 Map<String, BluetoothMapConvoContactElement> contactList = 1285 new HashMap<String, BluetoothMapConvoContactElement>(); 1286 try { 1287 if (c != null && c.moveToFirst()) { 1288 ConvoContactInfo cInfo = new ConvoContactInfo(); 1289 cInfo.setConvoColunms(c); 1290 do { 1291 long convoId = c.getLong(cInfo.mContactColConvoId); 1292 if (convoId == 0) 1293 continue; 1294 if (V) BluetoothMapUtils.printCursor(c); 1295 String uci = c.getString(cInfo.mContactColUci); 1296 String name = c.getString(cInfo.mContactColName); 1297 String displayName = c.getString(cInfo.mContactColNickname); 1298 String presenceStatus = c.getString(cInfo.mContactColPresenceText); 1299 int presenceState = c.getInt(cInfo.mContactColPresenceState); 1300 long lastActivity = c.getLong(cInfo.mContactColLastActive); 1301 int chatState = c.getInt(cInfo.mContactColChatState); 1302 int priority = c.getInt(cInfo.mContactColPriority); 1303 String btUid = c.getString(cInfo.mContactColBtUid); 1304 BluetoothMapConvoContactElement contact = 1305 new BluetoothMapConvoContactElement(uci, name, displayName, 1306 presenceStatus, presenceState, lastActivity, chatState, 1307 priority, btUid); 1308 contactList.put(uci, contact); 1309 } while (c.moveToNext()); 1310 } 1311 } finally { 1312 if (c != null) c.close(); 1313 } 1314 synchronized(getContactList()) { 1315 getContactList().clear(); 1316 setContactList(contactList, true); 1317 } 1318 } 1319 handleMsgListChangesSms()1320 private void handleMsgListChangesSms() { 1321 if (V) Log.d(TAG, "handleMsgListChangesSms"); 1322 1323 HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>(); 1324 boolean listChanged = false; 1325 1326 Cursor c; 1327 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1328 c = mResolver.query(Sms.CONTENT_URI, 1329 SMS_PROJECTION_SHORT, null, null, null); 1330 } else { 1331 c = mResolver.query(Sms.CONTENT_URI, 1332 SMS_PROJECTION_SHORT_EXT, null, null, null); 1333 } 1334 synchronized(getMsgListSms()) { 1335 try { 1336 if (c != null && c.moveToFirst()) { 1337 do { 1338 long id = c.getLong(c.getColumnIndex(Sms._ID)); 1339 int type = c.getInt(c.getColumnIndex(Sms.TYPE)); 1340 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 1341 int read = c.getInt(c.getColumnIndex(Sms.READ)); 1342 1343 Msg msg = getMsgListSms().remove(id); 1344 1345 /* We must filter out any actions made by the MCE, hence do not send e.g. 1346 * a message deleted and/or MessageShift for messages deleted by the MCE. */ 1347 1348 if (msg == null) { 1349 /* New message */ 1350 msg = new Msg(id, type, threadId, read); 1351 msgListSms.put(id, msg); 1352 listChanged = true; 1353 Event evt; 1354 if (mTransmitEvents == true && // extract contact details only if needed 1355 mMapEventReportVersion > 1356 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1357 String date = BluetoothMapUtils.getDateTimeString( 1358 c.getLong(c.getColumnIndex(Sms.DATE))); 1359 String subject = c.getString(c.getColumnIndex(Sms.BODY)); 1360 String name = ""; 1361 String phone = ""; 1362 if (type == 1) { //inbox 1363 phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); 1364 if (phone != null && !phone.isEmpty()) { 1365 name = BluetoothMapContent.getContactNameFromPhone(phone, 1366 mResolver); 1367 if(name == null || name.isEmpty()){ 1368 name = phone; 1369 } 1370 }else{ 1371 name = phone; 1372 } 1373 } else { 1374 TelephonyManager tm = 1375 (TelephonyManager)mContext.getSystemService( 1376 Context.TELEPHONY_SERVICE); 1377 if (tm != null) { 1378 phone = tm.getLine1Number(); 1379 name = tm.getLine1AlphaTag(); 1380 if(name == null || name.isEmpty()){ 1381 name = phone; 1382 } 1383 } 1384 } 1385 String priority = "no";// no priority for sms 1386 /* Incoming message from the network */ 1387 if (mMapEventReportVersion == 1388 BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1389 evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type), 1390 mSmsType, date, subject, name, priority); 1391 } else { 1392 evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type), 1393 mSmsType, date, subject, name, priority, 1394 (long)threadId, null); 1395 } 1396 } else { 1397 /* Incoming message from the network */ 1398 evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type), 1399 null, mSmsType); 1400 } 1401 sendEvent(evt); 1402 } else { 1403 /* Existing message */ 1404 if (type != msg.type) { 1405 listChanged = true; 1406 Log.d(TAG, "new type: " + type + " old type: " + msg.type); 1407 String oldFolder = getSmsFolderName(msg.type); 1408 String newFolder = getSmsFolderName(type); 1409 // Filter out the intermediate outbox steps 1410 if(!oldFolder.equalsIgnoreCase(newFolder)) { 1411 Event evt = new Event(EVENT_TYPE_SHIFT, id, 1412 getSmsFolderName(type), oldFolder, mSmsType); 1413 sendEvent(evt); 1414 } 1415 msg.type = type; 1416 } else if(threadId != msg.threadId) { 1417 listChanged = true; 1418 Log.d(TAG, "Message delete change: type: " + type 1419 + " old type: " + msg.type 1420 + "\n threadId: " + threadId 1421 + " old threadId: " + msg.threadId); 1422 if(threadId == DELETED_THREAD_ID) { // Message deleted 1423 // TODO: 1424 // We shall only use the folder attribute, but can't remember 1425 // wether to set it to "deleted" or the name of the folder 1426 // from which the message have been deleted. 1427 // "old_folder" used only for MessageShift event 1428 Event evt = new Event(EVENT_TYPE_DELETE, id, 1429 getSmsFolderName(msg.type), null, mSmsType); 1430 sendEvent(evt); 1431 msg.threadId = threadId; 1432 } else { // Undelete 1433 Event evt = new Event(EVENT_TYPE_SHIFT, id, 1434 getSmsFolderName(msg.type), 1435 BluetoothMapContract.FOLDER_NAME_DELETED, mSmsType); 1436 sendEvent(evt); 1437 msg.threadId = threadId; 1438 } 1439 } 1440 if(read != msg.flagRead) { 1441 listChanged = true; 1442 msg.flagRead = read; 1443 if (mMapEventReportVersion > 1444 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1445 Event evt = new Event(EVENT_TYPE_READ_STATUS, id, 1446 getSmsFolderName(msg.type), mSmsType); 1447 sendEvent(evt); 1448 } 1449 } 1450 msgListSms.put(id, msg); 1451 } 1452 } while (c.moveToNext()); 1453 } 1454 } finally { 1455 if (c != null) c.close(); 1456 } 1457 1458 for (Msg msg : getMsgListSms().values()) { 1459 // "old_folder" used only for MessageShift event 1460 Event evt = new Event(EVENT_TYPE_DELETE, msg.id, 1461 getSmsFolderName(msg.type), null, mSmsType); 1462 sendEvent(evt); 1463 listChanged = true; 1464 } 1465 1466 setMsgListSms(msgListSms, listChanged); 1467 } 1468 } 1469 handleMsgListChangesMms()1470 private void handleMsgListChangesMms() { 1471 if (V) Log.d(TAG, "handleMsgListChangesMms"); 1472 1473 HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>(); 1474 boolean listChanged = false; 1475 Cursor c; 1476 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1477 c = mResolver.query(Mms.CONTENT_URI, 1478 MMS_PROJECTION_SHORT, null, null, null); 1479 } else { 1480 c = mResolver.query(Mms.CONTENT_URI, 1481 MMS_PROJECTION_SHORT_EXT, null, null, null); 1482 } 1483 1484 synchronized(getMsgListMms()) { 1485 try{ 1486 if (c != null && c.moveToFirst()) { 1487 do { 1488 long id = c.getLong(c.getColumnIndex(Mms._ID)); 1489 int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 1490 int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE)); 1491 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 1492 // TODO: Go through code to see if we have an issue with mismatch in types 1493 // for threadId. Seems to be a long in DB?? 1494 int read = c.getInt(c.getColumnIndex(Mms.READ)); 1495 1496 Msg msg = getMsgListMms().remove(id); 1497 1498 /* We must filter out any actions made by the MCE, hence do not send 1499 * e.g. a message deleted and/or MessageShift for messages deleted by the 1500 * MCE.*/ 1501 1502 if (msg == null) { 1503 /* New message - only notify on retrieve conf */ 1504 listChanged = true; 1505 if (getMmsFolderName(type).equalsIgnoreCase( 1506 BluetoothMapContract.FOLDER_NAME_INBOX) && 1507 mtype != MESSAGE_TYPE_RETRIEVE_CONF) { 1508 continue; 1509 } 1510 msg = new Msg(id, type, threadId, read); 1511 msgListMms.put(id, msg); 1512 Event evt; 1513 if (mTransmitEvents == true && // extract contact details only if needed 1514 mMapEventReportVersion != 1515 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1516 String date = BluetoothMapUtils.getDateTimeString( 1517 c.getLong(c.getColumnIndex(Mms.DATE))); 1518 String subject = c.getString(c.getColumnIndex(Mms.SUBJECT)); 1519 if (subject == null || subject.length() == 0) { 1520 /* Get subject from mms text body parts - if any exists */ 1521 subject = BluetoothMapContent.getTextPartsMms(mResolver, id); 1522 } 1523 int tmpPri = c.getInt(c.getColumnIndex(Mms.PRIORITY)); 1524 Log.d(TAG, "TEMP handleMsgListChangesMms, " + 1525 "newMessage 'read' state: " + read + 1526 "priority: " + tmpPri); 1527 1528 String address = BluetoothMapContent.getAddressMms( 1529 mResolver,id,BluetoothMapContent.MMS_FROM); 1530 String priority = "no"; 1531 if(tmpPri == PduHeaders.PRIORITY_HIGH) 1532 priority = "yes"; 1533 1534 /* Incoming message from the network */ 1535 if (mMapEventReportVersion == 1536 BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1537 evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), 1538 TYPE.MMS, date, subject, address, priority); 1539 } else { 1540 evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), 1541 TYPE.MMS, date, subject, address, priority, 1542 (long)threadId, null); 1543 } 1544 1545 } else { 1546 /* Incoming message from the network */ 1547 evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), 1548 null, TYPE.MMS); 1549 } 1550 1551 sendEvent(evt); 1552 } else { 1553 /* Existing message */ 1554 if (type != msg.type) { 1555 Log.d(TAG, "new type: " + type + " old type: " + msg.type); 1556 Event evt; 1557 listChanged = true; 1558 if(msg.localInitiatedSend == false) { 1559 // Only send events about local initiated changes 1560 evt = new Event(EVENT_TYPE_SHIFT, id, getMmsFolderName(type), 1561 getMmsFolderName(msg.type), TYPE.MMS); 1562 sendEvent(evt); 1563 } 1564 msg.type = type; 1565 1566 if (getMmsFolderName(type).equalsIgnoreCase( 1567 BluetoothMapContract.FOLDER_NAME_SENT) 1568 && msg.localInitiatedSend == true) { 1569 // Stop tracking changes for this message 1570 msg.localInitiatedSend = false; 1571 evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id, 1572 getMmsFolderName(type), null, TYPE.MMS); 1573 sendEvent(evt); 1574 } 1575 } else if(threadId != msg.threadId) { 1576 Log.d(TAG, "Message delete change: type: " + type + " old type: " 1577 + msg.type 1578 + "\n threadId: " + threadId + " old threadId: " 1579 + msg.threadId); 1580 listChanged = true; 1581 if(threadId == DELETED_THREAD_ID) { // Message deleted 1582 // "old_folder" used only for MessageShift event 1583 Event evt = new Event(EVENT_TYPE_DELETE, id, 1584 getMmsFolderName(msg.type), null, TYPE.MMS); 1585 sendEvent(evt); 1586 msg.threadId = threadId; 1587 } else { // Undelete 1588 Event evt = new Event(EVENT_TYPE_SHIFT, id, 1589 getMmsFolderName(msg.type), 1590 BluetoothMapContract.FOLDER_NAME_DELETED, TYPE.MMS); 1591 sendEvent(evt); 1592 msg.threadId = threadId; 1593 } 1594 } 1595 if(read != msg.flagRead) { 1596 listChanged = true; 1597 msg.flagRead = read; 1598 if (mMapEventReportVersion > 1599 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1600 Event evt = new Event(EVENT_TYPE_READ_STATUS, id, 1601 getMmsFolderName(msg.type), TYPE.MMS); 1602 sendEvent(evt); 1603 } 1604 } 1605 msgListMms.put(id, msg); 1606 } 1607 } while (c.moveToNext()); 1608 1609 } 1610 } finally { 1611 if (c != null) c.close(); 1612 } 1613 for (Msg msg : getMsgListMms().values()) { 1614 // "old_folder" used only for MessageShift event 1615 Event evt = new Event(EVENT_TYPE_DELETE, msg.id, 1616 getMmsFolderName(msg.type), null, TYPE.MMS); 1617 sendEvent(evt); 1618 listChanged = true; 1619 } 1620 setMsgListMms(msgListMms, listChanged); 1621 } 1622 } 1623 handleMsgListChangesMsg(Uri uri)1624 private void handleMsgListChangesMsg(Uri uri) throws RemoteException{ 1625 if (V) Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString()); 1626 1627 // TODO: Change observer to handle accountId and message ID if present 1628 1629 HashMap<Long, Msg> msgList = new HashMap<Long, Msg>(); 1630 Cursor c; 1631 boolean listChanged = false; 1632 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1633 c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT, null, null, null); 1634 } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1635 c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT, null, null, null); 1636 } else { 1637 c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT2, null, null, null); 1638 } 1639 synchronized(getMsgListMsg()) { 1640 try { 1641 if (c != null && c.moveToFirst()) { 1642 do { 1643 long id = c.getLong(c.getColumnIndex( 1644 BluetoothMapContract.MessageColumns._ID)); 1645 int folderId = c.getInt(c.getColumnIndex( 1646 BluetoothMapContract.MessageColumns.FOLDER_ID)); 1647 int readFlag = c.getInt(c.getColumnIndex( 1648 BluetoothMapContract.MessageColumns.FLAG_READ)); 1649 Msg msg = getMsgListMsg().remove(id); 1650 BluetoothMapFolderElement folderElement = mFolders.getFolderById(folderId); 1651 String newFolder; 1652 if(folderElement != null) { 1653 newFolder = folderElement.getFullPath(); 1654 } else { 1655 // This can happen if a new folder is created while connected 1656 newFolder = "unknown"; 1657 } 1658 /* We must filter out any actions made by the MCE, hence do not send e.g. 1659 * a message deleted and/or MessageShift for messages deleted by the MCE. */ 1660 if (msg == null) { 1661 listChanged = true; 1662 /* New message - created with message unread */ 1663 msg = new Msg(id, folderId, 0, readFlag); 1664 msgList.put(id, msg); 1665 Event evt; 1666 /* Incoming message from the network */ 1667 if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1668 String date = BluetoothMapUtils.getDateTimeString( 1669 c.getLong(c.getColumnIndex( 1670 BluetoothMapContract.MessageColumns.DATE))); 1671 String subject = c.getString(c.getColumnIndex( 1672 BluetoothMapContract.MessageColumns.SUBJECT)); 1673 String address = c.getString(c.getColumnIndex( 1674 BluetoothMapContract.MessageColumns.FROM_LIST)); 1675 String priority = "no"; 1676 if(c.getInt(c.getColumnIndex( 1677 BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY)) 1678 == 1) 1679 priority = "yes"; 1680 if (mMapEventReportVersion == 1681 BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1682 evt = new Event(EVENT_TYPE_NEW, id, newFolder, 1683 mAccount.getType(), date, subject, address, priority); 1684 } else { 1685 long thread_id = c.getLong(c.getColumnIndex( 1686 BluetoothMapContract.MessageColumns.THREAD_ID)); 1687 String thread_name = c.getString(c.getColumnIndex( 1688 BluetoothMapContract.MessageColumns.THREAD_NAME)); 1689 evt = new Event(EVENT_TYPE_NEW, id, newFolder, 1690 mAccount.getType(), date, subject, address, priority, 1691 thread_id, thread_name); 1692 } 1693 } else { 1694 evt = new Event(EVENT_TYPE_NEW, id, newFolder, null, TYPE.EMAIL); 1695 } 1696 sendEvent(evt); 1697 } else { 1698 /* Existing message */ 1699 if (folderId != msg.folderId && msg.folderId != -1) { 1700 if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: " 1701 + msg.folderId); 1702 BluetoothMapFolderElement oldFolderElement = 1703 mFolders.getFolderById(msg.folderId); 1704 String oldFolder; 1705 listChanged = true; 1706 if(oldFolderElement != null) { 1707 oldFolder = oldFolderElement.getFullPath(); 1708 } else { 1709 // This can happen if a new folder is created while connected 1710 oldFolder = "unknown"; 1711 } 1712 BluetoothMapFolderElement deletedFolder = 1713 mFolders.getFolderByName( 1714 BluetoothMapContract.FOLDER_NAME_DELETED); 1715 BluetoothMapFolderElement sentFolder = 1716 mFolders.getFolderByName( 1717 BluetoothMapContract.FOLDER_NAME_SENT); 1718 /* 1719 * If the folder is now 'deleted', send a deleted-event in stead of 1720 * a shift or if message is sent initiated by MAP Client, then send 1721 * sending-success otherwise send folderShift 1722 */ 1723 if(deletedFolder != null && deletedFolder.getFolderId() 1724 == folderId) { 1725 // "old_folder" used only for MessageShift event 1726 Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder, 1727 null, mAccount.getType()); 1728 sendEvent(evt); 1729 } else if(sentFolder != null 1730 && sentFolder.getFolderId() == folderId 1731 && msg.localInitiatedSend == true) { 1732 if(msg.transparent) { 1733 mResolver.delete( 1734 ContentUris.withAppendedId(mMessageUri, id), 1735 null, null); 1736 } else { 1737 msg.localInitiatedSend = false; 1738 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, 1739 oldFolder, null, mAccount.getType()); 1740 sendEvent(evt); 1741 } 1742 } else { 1743 if (!oldFolder.equalsIgnoreCase("root")) { 1744 Event evt = new Event(EVENT_TYPE_SHIFT, id, newFolder, 1745 oldFolder, mAccount.getType()); 1746 sendEvent(evt); 1747 } 1748 } 1749 msg.folderId = folderId; 1750 } 1751 if(readFlag != msg.flagRead) { 1752 listChanged = true; 1753 1754 if (mMapEventReportVersion > 1755 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1756 Event evt = new Event(EVENT_TYPE_READ_STATUS, id, newFolder, 1757 mAccount.getType()); 1758 sendEvent(evt); 1759 msg.flagRead = readFlag; 1760 } 1761 } 1762 1763 msgList.put(id, msg); 1764 } 1765 } while (c.moveToNext()); 1766 } 1767 } finally { 1768 if (c != null) c.close(); 1769 } 1770 // For all messages no longer in the database send a delete notification 1771 for (Msg msg : getMsgListMsg().values()) { 1772 BluetoothMapFolderElement oldFolderElement = mFolders.getFolderById(msg.folderId); 1773 String oldFolder; 1774 listChanged = true; 1775 if(oldFolderElement != null) { 1776 oldFolder = oldFolderElement.getFullPath(); 1777 } else { 1778 oldFolder = "unknown"; 1779 } 1780 /* Some e-mail clients delete the message after sending, and creates a 1781 * new message in sent. We cannot track the message anymore, hence send both a 1782 * send success and delete message. 1783 */ 1784 if(msg.localInitiatedSend == true) { 1785 msg.localInitiatedSend = false; 1786 // If message is send with transparency don't set folder as message is deleted 1787 if (msg.transparent) 1788 oldFolder = null; 1789 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null, 1790 mAccount.getType()); 1791 sendEvent(evt); 1792 } 1793 /* As this message deleted is only send on a real delete - don't set folder. 1794 * - only send delete event if message is not sent with transparency 1795 */ 1796 if (!msg.transparent) { 1797 1798 // "old_folder" used only for MessageShift event 1799 Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder, 1800 null, mAccount.getType()); 1801 sendEvent(evt); 1802 } 1803 } 1804 setMsgListMsg(msgList, listChanged); 1805 } 1806 } 1807 handleMsgListChanges(Uri uri)1808 private void handleMsgListChanges(Uri uri) { 1809 if(uri.getAuthority().equals(mAuthority)) { 1810 try { 1811 if(D) Log.d(TAG, "handleMsgListChanges: account type = " 1812 + mAccount.getType().toString()); 1813 handleMsgListChangesMsg(uri); 1814 } catch(RemoteException e) { 1815 mMasInstance.restartObexServerSession(); 1816 Log.w(TAG, "Problems contacting the ContentProvider in mas Instance " 1817 + mMasId + " restaring ObexServerSession"); 1818 } 1819 1820 } 1821 // TODO: check to see if there could be problem with IM and SMS in one instance 1822 if (mEnableSmsMms) { 1823 handleMsgListChangesSms(); 1824 handleMsgListChangesMms(); 1825 } 1826 } 1827 handleContactListChanges(Uri uri)1828 private void handleContactListChanges(Uri uri) { 1829 if (uri.getAuthority().equals(mAuthority)) { 1830 try { 1831 if (V) Log.v(TAG,"handleContactListChanges uri: " + uri.toString()); 1832 Cursor c = null; 1833 boolean listChanged = false; 1834 try { 1835 ConvoContactInfo cInfo = new ConvoContactInfo(); 1836 1837 if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10 1838 && mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1839 c = mProviderClient 1840 .query(mContactUri, 1841 BluetoothMapContract. 1842 BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION, 1843 null, null, null); 1844 cInfo.setConvoColunms(c); 1845 } else { 1846 if (V) Log.v(TAG,"handleContactListChanges MAP version does not" + 1847 "support convocontact notifications"); 1848 return; 1849 } 1850 1851 HashMap<String, BluetoothMapConvoContactElement> contactList = 1852 new HashMap<String, 1853 BluetoothMapConvoContactElement>(getContactList().size()); 1854 1855 synchronized (getContactList()) { 1856 if (c != null && c.moveToFirst()) { 1857 do { 1858 String uci = c.getString(cInfo.mContactColUci); 1859 long convoId = c.getLong(cInfo.mContactColConvoId); 1860 if (convoId == 0) 1861 continue; 1862 1863 if (V) BluetoothMapUtils.printCursor(c); 1864 1865 BluetoothMapConvoContactElement contact = 1866 getContactList().remove(uci); 1867 1868 /* 1869 * We must filter out any actions made by the 1870 * MCE, hence do not send e.g. a message deleted 1871 * and/or MessageShift for messages deleted by 1872 * the MCE. 1873 */ 1874 if (contact == null) { 1875 listChanged = true; 1876 /* 1877 * New contact - added to conversation and 1878 * tracked here 1879 */ 1880 if (mMapEventReportVersion 1881 != BluetoothMapUtils.MAP_EVENT_REPORT_V10 1882 && mMapEventReportVersion 1883 != BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1884 Event evt; 1885 String name = c 1886 .getString(cInfo.mContactColName); 1887 String displayName = c 1888 .getString(cInfo.mContactColNickname); 1889 String presenceStatus = c 1890 .getString(cInfo.mContactColPresenceText); 1891 int presenceState = c 1892 .getInt(cInfo.mContactColPresenceState); 1893 long lastActivity = c 1894 .getLong(cInfo.mContactColLastActive); 1895 int chatState = c 1896 .getInt(cInfo.mContactColChatState); 1897 int priority = c 1898 .getInt(cInfo.mContactColPriority); 1899 String btUid = c 1900 .getString(cInfo.mContactColBtUid); 1901 1902 // Get Conversation information for 1903 // event 1904 // Uri convoUri = Uri 1905 // .parse(mAccount.mBase_uri 1906 // + "/" 1907 // + BluetoothMapContract.TABLE_CONVERSATION); 1908 // String whereClause = "contacts._id = " 1909 // + convoId; 1910 // Cursor cConvo = mProviderClient 1911 // .query(convoUri, 1912 // BluetoothMapContract.BT_CONVERSATION_PROJECTION, 1913 // whereClause, null, null); 1914 // TODO: will move out of the loop when merged with CB's 1915 // changes make sure to look up col index out side loop 1916 String convoName = null; 1917 // if (cConvo != null 1918 // && cConvo.moveToFirst()) { 1919 // convoName = cConvo 1920 // .getString(cConvo 1921 // .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME)); 1922 // } 1923 1924 contact = new BluetoothMapConvoContactElement( 1925 uci, name, displayName, 1926 presenceStatus, presenceState, 1927 lastActivity, chatState, 1928 priority, btUid); 1929 1930 contactList.put(uci, contact); 1931 1932 evt = new Event( 1933 EVENT_TYPE_CONVERSATION, 1934 uci, 1935 mAccount.getType(), 1936 name, 1937 String.valueOf(priority), 1938 BluetoothMapUtils 1939 .getDateTimeString(lastActivity), 1940 convoId, convoName, 1941 presenceState, presenceStatus, 1942 chatState); 1943 1944 sendEvent(evt); 1945 } 1946 1947 } else { 1948 // Not new - compare updated content 1949 // Uri convoUri = Uri 1950 // .parse(mAccount.mBase_uri 1951 // + "/" 1952 // + BluetoothMapContract.TABLE_CONVERSATION); 1953 // TODO: Should be changed to own provider interface name 1954 // String whereClause = "contacts._id = " 1955 // + convoId; 1956 // Cursor cConvo = mProviderClient 1957 // .query(convoUri, 1958 // BluetoothMapContract.BT_CONVERSATION_PROJECTION, 1959 // whereClause, null, null); 1960 // // TODO: will move out of the loop when merged with CB's 1961 // // changes make sure to look up col index out side loop 1962 String convoName = null; 1963 // if (cConvo != null && cConvo.moveToFirst()) { 1964 // convoName = cConvo 1965 // .getString(cConvo 1966 // .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME)); 1967 // } 1968 1969 // Check if presence is updated 1970 int presenceState = c.getInt(cInfo.mContactColPresenceState); 1971 String presenceStatus = c.getString( 1972 cInfo.mContactColPresenceText); 1973 String currentPresenceStatus = contact 1974 .getPresenceStatus(); 1975 if (contact.getPresenceAvailability() != presenceState 1976 || currentPresenceStatus != presenceStatus) { 1977 long lastOnline = c 1978 .getLong(cInfo.mContactColLastOnline); 1979 contact.setPresenceAvailability(presenceState); 1980 contact.setLastActivity(lastOnline); 1981 if (currentPresenceStatus != null 1982 && !currentPresenceStatus 1983 .equals(presenceStatus)) { 1984 contact.setPresenceStatus(presenceStatus); 1985 } 1986 Event evt = new Event( 1987 EVENT_TYPE_PRESENCE, 1988 uci, 1989 mAccount.getType(), 1990 contact.getName(), 1991 String.valueOf(contact 1992 .getPriority()), 1993 BluetoothMapUtils 1994 .getDateTimeString(lastOnline), 1995 convoId, convoName, 1996 presenceState, presenceStatus, 1997 0); 1998 sendEvent(evt); 1999 } 2000 2001 // Check if chat state is updated 2002 int chatState = c.getInt(cInfo.mContactColChatState); 2003 if (contact.getChatState() != chatState) { 2004 // Get DB timestamp 2005 long lastActivity = c.getLong(cInfo.mContactColLastActive); 2006 contact.setLastActivity(lastActivity); 2007 contact.setChatState(chatState); 2008 Event evt = new Event( 2009 EVENT_TYPE_CHAT_STATE, 2010 uci, 2011 mAccount.getType(), 2012 contact.getName(), 2013 String.valueOf(contact 2014 .getPriority()), 2015 BluetoothMapUtils 2016 .getDateTimeString(lastActivity), 2017 convoId, convoName, 0, null, 2018 chatState); 2019 sendEvent(evt); 2020 } 2021 contactList.put(uci, contact); 2022 } 2023 } while (c.moveToNext()); 2024 } 2025 if(getContactList().size() > 0) { 2026 // one or more contacts were deleted, hence the conversation listing 2027 // version counter should change. 2028 listChanged = true; 2029 } 2030 setContactList(contactList, listChanged); 2031 } // end synchronized 2032 } finally { 2033 if (c != null) c.close(); 2034 } 2035 } catch (RemoteException e) { 2036 mMasInstance.restartObexServerSession(); 2037 Log.w(TAG, 2038 "Problems contacting the ContentProvider in mas Instance " 2039 + mMasId + " restaring ObexServerSession"); 2040 } 2041 2042 } 2043 // TODO: conversation contact updates if IM and SMS(MMS in one instance 2044 } 2045 setEmailMessageStatusDelete(BluetoothMapFolderElement mCurrentFolder, String uriStr, long handle, int status)2046 private boolean setEmailMessageStatusDelete(BluetoothMapFolderElement mCurrentFolder, 2047 String uriStr, long handle, int status) { 2048 boolean res = false; 2049 Uri uri = Uri.parse(uriStr + BluetoothMapContract.TABLE_MESSAGE); 2050 2051 int updateCount = 0; 2052 ContentValues contentValues = new ContentValues(); 2053 BluetoothMapFolderElement deleteFolder = mFolders. 2054 getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED); 2055 contentValues.put(BluetoothMapContract.MessageColumns._ID, handle); 2056 synchronized(getMsgListMsg()) { 2057 Msg msg = getMsgListMsg().get(handle); 2058 if (status == BluetoothMapAppParams.STATUS_VALUE_YES) { 2059 /* Set deleted folder id */ 2060 long folderId = -1; 2061 if(deleteFolder != null) { 2062 folderId = deleteFolder.getFolderId(); 2063 } 2064 contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID,folderId); 2065 updateCount = mResolver.update(uri, contentValues, null, null); 2066 /* The race between updating the value in our cached values and the database 2067 * is handled by the synchronized statement. */ 2068 if(updateCount > 0) { 2069 res = true; 2070 if (msg != null) { 2071 msg.oldFolderId = msg.folderId; 2072 /* Update the folder ID to avoid triggering an event for MCE 2073 * initiated actions. */ 2074 msg.folderId = folderId; 2075 } 2076 if(D) Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId); 2077 } else { 2078 Log.w(TAG, "Msg: " + handle + " - Set delete status " + status 2079 + " failed for folderId " + folderId); 2080 } 2081 } else if (status == BluetoothMapAppParams.STATUS_VALUE_NO) { 2082 /* Undelete message. move to old folder if we know it, 2083 * else move to inbox - as dictated by the spec. */ 2084 if(msg != null && deleteFolder != null && 2085 msg.folderId == deleteFolder.getFolderId()) { 2086 /* Only modify messages in the 'Deleted' folder */ 2087 long folderId = -1; 2088 BluetoothMapFolderElement inboxFolder = mCurrentFolder. 2089 getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX); 2090 if (msg != null && msg.oldFolderId != -1) { 2091 folderId = msg.oldFolderId; 2092 } else { 2093 if(inboxFolder != null) { 2094 folderId = inboxFolder.getFolderId(); 2095 } 2096 if(D)Log.d(TAG,"We did not delete the message, hence the old folder " + 2097 "is unknown. Moving to inbox."); 2098 } 2099 contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId); 2100 updateCount = mResolver.update(uri, contentValues, null, null); 2101 if(updateCount > 0) { 2102 res = true; 2103 /* Update the folder ID to avoid triggering an event for MCE 2104 * initiated actions. */ 2105 /* UPDATE: Actually the BT-Spec. states that an undelete is a move of the 2106 * message to INBOX - clearified in errata 5591. 2107 * Therefore we update the cache to INBOX-folderId - to trigger a message 2108 * shift event to the old-folder. */ 2109 if(inboxFolder != null) { 2110 msg.folderId = inboxFolder.getFolderId(); 2111 } else { 2112 msg.folderId = folderId; 2113 } 2114 } else { 2115 if(D)Log.d(TAG,"We did not delete the message, hence the old folder " + 2116 "is unknown. Moving to inbox."); 2117 } 2118 } 2119 } 2120 if(V) { 2121 BluetoothMapFolderElement folderElement; 2122 String folderName = "unknown"; 2123 if (msg != null) { 2124 folderElement = mCurrentFolder.getFolderById(msg.folderId); 2125 if(folderElement != null) { 2126 folderName = folderElement.getName(); 2127 } 2128 } 2129 Log.d(TAG,"setEmailMessageStatusDelete: " + handle + " from " + folderName 2130 + " status: " + status); 2131 } 2132 } 2133 if(res == false) { 2134 Log.w(TAG, "Set delete status " + status + " failed."); 2135 } 2136 return res; 2137 } 2138 updateThreadId(Uri uri, String valueString, long threadId)2139 private void updateThreadId(Uri uri, String valueString, long threadId) { 2140 ContentValues contentValues = new ContentValues(); 2141 contentValues.put(valueString, threadId); 2142 mResolver.update(uri, contentValues, null, null); 2143 } 2144 deleteMessageMms(long handle)2145 private boolean deleteMessageMms(long handle) { 2146 boolean res = false; 2147 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 2148 Cursor c = mResolver.query(uri, null, null, null, null); 2149 try { 2150 if (c != null && c.moveToFirst()) { 2151 /* Move to deleted folder, or delete if already in deleted folder */ 2152 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 2153 if (threadId != DELETED_THREAD_ID) { 2154 /* Set deleted thread id */ 2155 synchronized(getMsgListMms()) { 2156 Msg msg = getMsgListMms().get(handle); 2157 if(msg != null) { // This will always be the case 2158 msg.threadId = DELETED_THREAD_ID; 2159 } 2160 } 2161 updateThreadId(uri, Mms.THREAD_ID, DELETED_THREAD_ID); 2162 } else { 2163 /* Delete from observer message list to avoid delete notifications */ 2164 synchronized(getMsgListMms()) { 2165 getMsgListMms().remove(handle); 2166 } 2167 /* Delete message */ 2168 mResolver.delete(uri, null, null); 2169 } 2170 res = true; 2171 } 2172 } finally { 2173 if (c != null) c.close(); 2174 } 2175 2176 return res; 2177 } 2178 unDeleteMessageMms(long handle)2179 private boolean unDeleteMessageMms(long handle) { 2180 boolean res = false; 2181 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 2182 Cursor c = mResolver.query(uri, null, null, null, null); 2183 try { 2184 if (c != null && c.moveToFirst()) { 2185 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 2186 if (threadId == DELETED_THREAD_ID) { 2187 /* Restore thread id from address, or if no thread for address 2188 * create new thread by insert and remove of fake message */ 2189 String address; 2190 long id = c.getLong(c.getColumnIndex(Mms._ID)); 2191 int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 2192 if (msgBox == Mms.MESSAGE_BOX_INBOX) { 2193 address = BluetoothMapContent.getAddressMms(mResolver, id, 2194 BluetoothMapContent.MMS_FROM); 2195 } else { 2196 address = BluetoothMapContent.getAddressMms(mResolver, id, 2197 BluetoothMapContent.MMS_TO); 2198 } 2199 Set<String> recipients = new HashSet<String>(); 2200 recipients.addAll(Arrays.asList(address)); 2201 Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients); 2202 synchronized(getMsgListMms()) { 2203 Msg msg = getMsgListMms().get(handle); 2204 if(msg != null) { // This will always be the case 2205 msg.threadId = oldThreadId.intValue(); 2206 // Spec. states that undelete shall shift the message to Inbox. 2207 // Hence we need to trigger a message shift from INBOX to old-folder 2208 // after undelete. 2209 // We do this by changing the cached folder value to being inbox - hence 2210 // the event handler will se the update as the message have been shifted 2211 // from INBOX to old-folder. (Errata 5591 clearifies this) 2212 msg.type = Mms.MESSAGE_BOX_INBOX; 2213 } 2214 } 2215 updateThreadId(uri, Mms.THREAD_ID, oldThreadId); 2216 } else { 2217 Log.d(TAG, "Message not in deleted folder: handle " + handle 2218 + " threadId " + threadId); 2219 } 2220 res = true; 2221 } 2222 } finally { 2223 if (c != null) c.close(); 2224 } 2225 return res; 2226 } 2227 deleteMessageSms(long handle)2228 private boolean deleteMessageSms(long handle) { 2229 boolean res = false; 2230 Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle); 2231 Cursor c = mResolver.query(uri, null, null, null, null); 2232 try { 2233 if (c != null && c.moveToFirst()) { 2234 /* Move to deleted folder, or delete if already in deleted folder */ 2235 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 2236 if (threadId != DELETED_THREAD_ID) { 2237 synchronized(getMsgListSms()) { 2238 Msg msg = getMsgListSms().get(handle); 2239 if(msg != null) { // This will always be the case 2240 msg.threadId = DELETED_THREAD_ID; 2241 } 2242 } 2243 /* Set deleted thread id */ 2244 updateThreadId(uri, Sms.THREAD_ID, DELETED_THREAD_ID); 2245 } else { 2246 /* Delete from observer message list to avoid delete notifications */ 2247 synchronized(getMsgListSms()) { 2248 getMsgListSms().remove(handle); 2249 } 2250 /* Delete message */ 2251 mResolver.delete(uri, null, null); 2252 } 2253 res = true; 2254 } 2255 } finally { 2256 if (c != null) c.close(); 2257 } 2258 return res; 2259 } 2260 unDeleteMessageSms(long handle)2261 private boolean unDeleteMessageSms(long handle) { 2262 boolean res = false; 2263 Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle); 2264 Cursor c = mResolver.query(uri, null, null, null, null); 2265 try { 2266 if (c != null && c.moveToFirst()) { 2267 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 2268 if (threadId == DELETED_THREAD_ID) { 2269 String address = c.getString(c.getColumnIndex(Sms.ADDRESS)); 2270 Set<String> recipients = new HashSet<String>(); 2271 recipients.addAll(Arrays.asList(address)); 2272 Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients); 2273 synchronized(getMsgListSms()) { 2274 Msg msg = getMsgListSms().get(handle); 2275 if(msg != null) { 2276 msg.threadId = oldThreadId.intValue(); 2277 /* This will always be the case 2278 * The threadId is specified as an int, so it is safe to truncate 2279 * TODO: Test that this will trigger a message-shift from Inbox 2280 * to old-folder 2281 **/ 2282 /* Spec. states that undelete shall shift the message to Inbox. 2283 * Hence we need to trigger a message shift from INBOX to old-folder 2284 * after undelete. 2285 * We do this by changing the cached folder value to being inbox - hence 2286 * the event handler will se the update as the message have been shifted 2287 * from INBOX to old-folder. (Errata 5591 clearifies this) 2288 * */ 2289 msg.type = Sms.MESSAGE_TYPE_INBOX; 2290 } 2291 } 2292 updateThreadId(uri, Sms.THREAD_ID, oldThreadId); 2293 } else { 2294 Log.d(TAG, "Message not in deleted folder: handle " + handle 2295 + " threadId " + threadId); 2296 } 2297 res = true; 2298 } 2299 } finally { 2300 if (c != null) c.close(); 2301 } 2302 return res; 2303 } 2304 2305 /** 2306 * 2307 * @param handle 2308 * @param type 2309 * @param mCurrentFolder 2310 * @param uriStr 2311 * @param statusValue 2312 * @return true is success 2313 */ setMessageStatusDeleted(long handle, TYPE type, BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue)2314 public boolean setMessageStatusDeleted(long handle, TYPE type, 2315 BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue) { 2316 boolean res = false; 2317 if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle 2318 + " type " + type + " value " + statusValue); 2319 2320 if (type == TYPE.EMAIL) { 2321 res = setEmailMessageStatusDelete(mCurrentFolder, uriStr, handle, statusValue); 2322 } else if (type == TYPE.IM) { 2323 // TODO: to do when deleting IM message 2324 if (D) Log.d(TAG, "setMessageStatusDeleted: IM not handled" ); 2325 } else { 2326 if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) { 2327 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) { 2328 res = deleteMessageSms(handle); 2329 } else if (type == TYPE.MMS) { 2330 res = deleteMessageMms(handle); 2331 } 2332 } else if (statusValue == BluetoothMapAppParams.STATUS_VALUE_NO) { 2333 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) { 2334 res = unDeleteMessageSms(handle); 2335 } else if (type == TYPE.MMS) { 2336 res = unDeleteMessageMms(handle); 2337 } 2338 } 2339 } 2340 return res; 2341 } 2342 2343 /** 2344 * 2345 * @param handle 2346 * @param type 2347 * @param uriStr 2348 * @param statusValue 2349 * @return true at success 2350 */ setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue)2351 public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue) 2352 throws RemoteException{ 2353 int count = 0; 2354 2355 if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle 2356 + " type " + type + " value " + statusValue); 2357 2358 /* Approved MAP spec errata 3445 states that read status initiated 2359 * by the MCE shall change the MSE read status. */ 2360 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) { 2361 Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle); 2362 ContentValues contentValues = new ContentValues(); 2363 contentValues.put(Sms.READ, statusValue); 2364 contentValues.put(Sms.SEEN, statusValue); 2365 String values = contentValues.toString(); 2366 if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() + " values " + values); 2367 synchronized(getMsgListSms()) { 2368 Msg msg = getMsgListSms().get(handle); 2369 if(msg != null) { // This will always be the case 2370 msg.flagRead = statusValue; 2371 } 2372 } 2373 count = mResolver.update(uri, contentValues, null, null); 2374 if (D) Log.d(TAG, " -> "+count +" rows updated!"); 2375 2376 } else if (type == TYPE.MMS) { 2377 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 2378 if (D) Log.d(TAG, " -> MMS Uri: " + uri.toString()); 2379 ContentValues contentValues = new ContentValues(); 2380 contentValues.put(Mms.READ, statusValue); 2381 synchronized(getMsgListMms()) { 2382 Msg msg = getMsgListMms().get(handle); 2383 if(msg != null) { // This will always be the case 2384 msg.flagRead = statusValue; 2385 } 2386 } 2387 count = mResolver.update(uri, contentValues, null, null); 2388 if (D) Log.d(TAG, " -> "+count +" rows updated!"); 2389 } else if (type == TYPE.EMAIL || 2390 type == TYPE.IM) { 2391 Uri uri = mMessageUri; 2392 ContentValues contentValues = new ContentValues(); 2393 contentValues.put(BluetoothMapContract.MessageColumns.FLAG_READ, statusValue); 2394 contentValues.put(BluetoothMapContract.MessageColumns._ID, handle); 2395 synchronized(getMsgListMsg()) { 2396 Msg msg = getMsgListMsg().get(handle); 2397 if(msg != null) { // This will always be the case 2398 msg.flagRead = statusValue; 2399 } 2400 } 2401 count = mProviderClient.update(uri, contentValues, null, null); 2402 } 2403 2404 return (count > 0); 2405 } 2406 2407 private class PushMsgInfo { 2408 long id; 2409 int transparent; 2410 int retry; 2411 String phone; 2412 Uri uri; 2413 long timestamp; 2414 int parts; 2415 int partsSent; 2416 int partsDelivered; 2417 boolean resend; 2418 boolean sendInProgress; 2419 boolean failedSent; // Set to true if a single part sent fail is received. 2420 int statusDelivered; // Set to != 0 if a single part deliver fail is received. 2421 PushMsgInfo(long id, int transparent, int retry, String phone, Uri uri)2422 public PushMsgInfo(long id, int transparent, 2423 int retry, String phone, Uri uri) { 2424 this.id = id; 2425 this.transparent = transparent; 2426 this.retry = retry; 2427 this.phone = phone; 2428 this.uri = uri; 2429 this.resend = false; 2430 this.sendInProgress = false; 2431 this.failedSent = false; 2432 this.statusDelivered = 0; /* Assume success */ 2433 this.timestamp = 0; 2434 }; 2435 } 2436 2437 private Map<Long, PushMsgInfo> mPushMsgList = 2438 Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>()); 2439 pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap, String emailBaseUri)2440 public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement, 2441 BluetoothMapAppParams ap, String emailBaseUri) 2442 throws IllegalArgumentException, RemoteException, IOException { 2443 if (D) Log.d(TAG, "pushMessage"); 2444 ArrayList<BluetoothMapbMessage.vCard> recipientList = msg.getRecipients(); 2445 int transparent = (ap.getTransparent() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? 2446 0 : ap.getTransparent(); 2447 int retry = ap.getRetry(); 2448 int charset = ap.getCharset(); 2449 long handle = -1; 2450 long folderId = -1; 2451 2452 if (recipientList == null) { 2453 if (folderElement.getName().equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) { 2454 BluetoothMapbMessage.vCard empty = 2455 new BluetoothMapbMessage.vCard("", "", null, null, 0); 2456 recipientList = new ArrayList<BluetoothMapbMessage.vCard>(); 2457 recipientList.add(empty); 2458 Log.w(TAG, "Added empty recipient to draft message"); 2459 } else { 2460 Log.e(TAG, "Trying to send a message with no recipients"); 2461 return -1; 2462 } 2463 } 2464 2465 if ( msg.getType().equals(TYPE.EMAIL) ) { 2466 /* Write the message to the database */ 2467 String msgBody = ((BluetoothMapbMessageEmail) msg).getEmailBody(); 2468 if (V) { 2469 int length = msgBody.length(); 2470 Log.v(TAG, "pushMessage: message string length = " + length); 2471 String messages[] = msgBody.split("\r\n"); 2472 Log.v(TAG, "pushMessage: messages count=" + messages.length); 2473 for(int i = 0; i < messages.length; i++) { 2474 Log.v(TAG, "part " + i + ":" + messages[i]); 2475 } 2476 } 2477 FileOutputStream os = null; 2478 ParcelFileDescriptor fdOut = null; 2479 Uri uriInsert = Uri.parse(emailBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2480 if (D) Log.d(TAG, "pushMessage - uriInsert= " + uriInsert.toString() + 2481 ", intoFolder id=" + folderElement.getFolderId()); 2482 2483 synchronized(getMsgListMsg()) { 2484 // Now insert the empty message into folder 2485 ContentValues values = new ContentValues(); 2486 folderId = folderElement.getFolderId(); 2487 values.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId); 2488 Uri uriNew = mProviderClient.insert(uriInsert, values); 2489 if (D) Log.d(TAG, "pushMessage - uriNew= " + uriNew.toString()); 2490 handle = Long.parseLong(uriNew.getLastPathSegment()); 2491 2492 try { 2493 fdOut = mProviderClient.openFile(uriNew, "w"); 2494 os = new FileOutputStream(fdOut.getFileDescriptor()); 2495 // Write Email to DB 2496 os.write(msgBody.getBytes(), 0, msgBody.getBytes().length); 2497 } catch (FileNotFoundException e) { 2498 Log.w(TAG, e); 2499 throw(new IOException("Unable to open file stream")); 2500 } catch (NullPointerException e) { 2501 Log.w(TAG, e); 2502 throw(new IllegalArgumentException("Unable to parse message.")); 2503 } finally { 2504 try { 2505 if(os != null) 2506 os.close(); 2507 } catch (IOException e) {Log.w(TAG, e);} 2508 try { 2509 if(fdOut != null) 2510 fdOut.close(); 2511 } catch (IOException e) {Log.w(TAG, e);} 2512 } 2513 2514 /* Extract the data for the inserted message, and store in local mirror, to 2515 * avoid sending a NewMessage Event. */ 2516 /*TODO: We need to add the new 1.1 parameter as well:-) e.g. read*/ 2517 Msg newMsg = new Msg(handle, folderId, 1); // TODO: Create define for read-state 2518 newMsg.transparent = (transparent == 1) ? true : false; 2519 if ( folderId == folderElement.getFolderByName( 2520 BluetoothMapContract.FOLDER_NAME_OUTBOX).getFolderId() ) { 2521 newMsg.localInitiatedSend = true; 2522 } 2523 getMsgListMsg().put(handle, newMsg); 2524 } 2525 } else { // type SMS_* of MMS 2526 for (BluetoothMapbMessage.vCard recipient : recipientList) { 2527 // Only send the message to the top level recipient 2528 if(recipient.getEnvLevel() == 0) 2529 { 2530 /* Only send to first address */ 2531 String phone = recipient.getFirstPhoneNumber(); 2532 String email = recipient.getFirstEmail(); 2533 String folder = folderElement.getName(); 2534 boolean read = false; 2535 boolean deliveryReport = true; 2536 String msgBody = null; 2537 2538 /* If MMS contains text only and the size is less than ten SMS's 2539 * then convert the MMS to type SMS and then proceed 2540 */ 2541 if (msg.getType().equals(TYPE.MMS) && 2542 (((BluetoothMapbMessageMime) msg).getTextOnly() == true)) { 2543 msgBody = ((BluetoothMapbMessageMime) msg).getMessageAsText(); 2544 SmsManager smsMng = SmsManager.getDefault(); 2545 ArrayList<String> parts = smsMng.divideMessage(msgBody); 2546 int smsParts = parts.size(); 2547 if (smsParts <= CONVERT_MMS_TO_SMS_PART_COUNT ) { 2548 if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts=" 2549 + smsParts ); 2550 msg.setType(mSmsType); 2551 } else { 2552 if (D) Log.d(TAG, "pushMessage - MMS text only but to big to " + 2553 "convert to SMS"); 2554 msgBody = null; 2555 } 2556 2557 } 2558 2559 if (msg.getType().equals(TYPE.MMS)) { 2560 /* Send message if folder is outbox else just store in draft*/ 2561 handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMime)msg, 2562 transparent, retry); 2563 } else if (msg.getType().equals(TYPE.SMS_GSM) || 2564 msg.getType().equals(TYPE.SMS_CDMA) ) { 2565 /* Add the message to the database */ 2566 if(msgBody == null) 2567 msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody(); 2568 2569 if (TextUtils.isEmpty(msgBody)) { 2570 Log.d(TAG, "PushMsg: Empty msgBody "); 2571 /* not allowed to push empty message */ 2572 throw new IllegalArgumentException("push EMPTY message: Invalid Body"); 2573 } 2574 /* We need to lock the SMS list while updating the database, 2575 * to avoid sending events on MCE initiated operation. */ 2576 Uri contentUri = Uri.parse(Sms.CONTENT_URI+ "/" + folder); 2577 Uri uri; 2578 synchronized(getMsgListSms()) { 2579 uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody, 2580 "", System.currentTimeMillis(), read, deliveryReport); 2581 2582 if(V) Log.v(TAG, "Sms.addMessageToUri() returned: " + uri); 2583 if (uri == null) { 2584 if (D) Log.d(TAG, "pushMessage - failure on add to uri " 2585 + contentUri); 2586 return -1; 2587 } 2588 Cursor c = mResolver.query(uri, SMS_PROJECTION_SHORT, null, null, null); 2589 2590 /* Extract the data for the inserted message, and store in local mirror, 2591 * to avoid sending a NewMessage Event. */ 2592 try { 2593 if (c != null && c.moveToFirst()) { 2594 long id = c.getLong(c.getColumnIndex(Sms._ID)); 2595 int type = c.getInt(c.getColumnIndex(Sms.TYPE)); 2596 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 2597 int readFlag = c.getInt(c.getColumnIndex(Sms.READ)); 2598 if(V) Log.v(TAG, "add message with id=" + id + 2599 " type=" + type + " threadId=" + threadId + 2600 " readFlag=" + readFlag + "to mMsgListSms"); 2601 Msg newMsg = new Msg(id, type, threadId, readFlag); 2602 getMsgListSms().put(id, newMsg); 2603 c.close(); 2604 } else { 2605 Log.w(TAG,"Message: " + uri + " no longer exist!"); 2606 /* This can only happen, if the message is deleted 2607 * just as it is added */ 2608 return -1; 2609 } 2610 } finally { 2611 if (c != null) c.close(); 2612 } 2613 2614 handle = Long.parseLong(uri.getLastPathSegment()); 2615 2616 /* Send message if folder is outbox */ 2617 if (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) { 2618 PushMsgInfo msgInfo = new PushMsgInfo(handle, transparent, 2619 retry, phone, uri); 2620 mPushMsgList.put(handle, msgInfo); 2621 sendMessage(msgInfo, msgBody); 2622 if(V) Log.v(TAG, "sendMessage returned..."); 2623 } /* else just added to draft */ 2624 2625 /* sendMessage causes the message to be deleted and reinserted, 2626 * hence we need to lock the list while this is happening. */ 2627 } 2628 } else { 2629 if (D) Log.d(TAG, "pushMessage - failure on type " ); 2630 return -1; 2631 } 2632 } 2633 } 2634 } 2635 2636 /* If multiple recipients return handle of last */ 2637 return handle; 2638 } 2639 sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMime msg, int transparent, int retry)2640 public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMime msg, 2641 int transparent, int retry) { 2642 /* 2643 *strategy: 2644 *1) parse message into parts 2645 *if folder is outbox/drafts: 2646 *2) push message to draft 2647 *if folder is outbox: 2648 *3) move message to outbox (to trigger the mms app to add msg to pending_messages list) 2649 *4) send intent to mms app in order to wake it up. 2650 *else if folder !outbox: 2651 *1) push message to folder 2652 * */ 2653 if (folder != null && (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) 2654 || folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT))) { 2655 long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, to_address, msg); 2656 /* if invalid handle (-1) then just return the handle 2657 * - else continue sending (if folder is outbox) */ 2658 if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle && 2659 folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) { 2660 Uri btMmsUri = MmsFileProvider.CONTENT_URI.buildUpon() 2661 .appendPath(Long.toString(handle)).build(); 2662 Intent sentIntent = new Intent(ACTION_MESSAGE_SENT); 2663 // TODO: update the mmsMsgList <- done in pushMmsToFolder() but check 2664 sentIntent.setType("message/" + Long.toString(handle)); 2665 sentIntent.putExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.MMS.ordinal()); 2666 sentIntent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, handle); // needed for notification 2667 sentIntent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, transparent); 2668 sentIntent.putExtra(EXTRA_MESSAGE_SENT_RETRY, retry); 2669 //sentIntent.setDataAndNormalize(btMmsUri); 2670 PendingIntent pendingSendIntent = PendingIntent.getBroadcast(mContext, 0, 2671 sentIntent, 0); 2672 SmsManager.getDefault().sendMultimediaMessage(mContext, 2673 btMmsUri, null/*locationUrl*/, null/*configOverrides*/, 2674 pendingSendIntent); 2675 } 2676 return handle; 2677 } else { 2678 /* not allowed to push mms to anything but outbox/draft */ 2679 throw new IllegalArgumentException("Cannot push message to other " + 2680 "folders than outbox/draft"); 2681 } 2682 } 2683 moveDraftToOutbox(long handle)2684 private void moveDraftToOutbox(long handle) { 2685 moveMmsToFolder(handle, mResolver, Mms.MESSAGE_BOX_OUTBOX); 2686 } 2687 2688 /** 2689 * Move a MMS to another folder. 2690 * @param handle the CP handle of the message to move 2691 * @param resolver the ContentResolver to use 2692 * @param folder the destination folder - use Mms.MESSAGE_BOX_xxx 2693 */ moveMmsToFolder(long handle, ContentResolver resolver, int folder)2694 private static void moveMmsToFolder(long handle, ContentResolver resolver, int folder) { 2695 /*Move message by changing the msg_box value in the content provider database */ 2696 if (handle != -1) { 2697 String whereClause = " _id= " + handle; 2698 Uri uri = Mms.CONTENT_URI; 2699 Cursor queryResult = resolver.query(uri, null, whereClause, null, null); 2700 try { 2701 if (queryResult != null) { 2702 if (queryResult.getCount() > 0) { 2703 queryResult.moveToFirst(); 2704 ContentValues data = new ContentValues(); 2705 /* set folder to be outbox */ 2706 data.put(Mms.MESSAGE_BOX, folder); 2707 resolver.update(uri, data, whereClause, null); 2708 if (D) Log.d(TAG, "moved MMS message to " + getMmsFolderName(folder)); 2709 } 2710 } else { 2711 Log.w(TAG, "Could not move MMS message to " + getMmsFolderName(folder)); 2712 } 2713 } finally { 2714 if (queryResult != null) queryResult.close(); 2715 } 2716 } 2717 } pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMime msg)2718 private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMime msg) { 2719 /** 2720 * strategy: 2721 * 1) parse msg into parts + header 2722 * 2) create thread id (abuse the ease of adding an SMS to get id for thread) 2723 * 3) push parts into content://mms/parts/ table 2724 * 3) 2725 */ 2726 2727 ContentValues values = new ContentValues(); 2728 values.put(Mms.MESSAGE_BOX, folder); 2729 values.put(Mms.READ, 0); 2730 values.put(Mms.SEEN, 0); 2731 if(msg.getSubject() != null) { 2732 values.put(Mms.SUBJECT, msg.getSubject()); 2733 } else { 2734 values.put(Mms.SUBJECT, ""); 2735 } 2736 2737 if(msg.getSubject() != null && msg.getSubject().length() > 0) { 2738 values.put(Mms.SUBJECT_CHARSET, 106); 2739 } 2740 values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related"); 2741 values.put(Mms.EXPIRY, 604800); 2742 values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR); 2743 values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ); 2744 values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION); 2745 values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL); 2746 values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO); 2747 values.put(Mms.TRANSACTION_ID, "T"+ Long.toHexString(System.currentTimeMillis())); 2748 values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO); 2749 values.put(Mms.LOCKED, 0); 2750 if(msg.getTextOnly() == true) 2751 values.put(Mms.TEXT_ONLY, true); 2752 values.put(Mms.MESSAGE_SIZE, msg.getSize()); 2753 2754 // Get thread id 2755 Set<String> recipients = new HashSet<String>(); 2756 recipients.addAll(Arrays.asList(to_address)); 2757 values.put(Mms.THREAD_ID, Telephony.Threads.getOrCreateThreadId(mContext, recipients)); 2758 Uri uri = Mms.CONTENT_URI; 2759 2760 synchronized (getMsgListMms()) { 2761 2762 uri = mResolver.insert(uri, values); 2763 2764 if (uri == null) { 2765 // unable to insert MMS 2766 Log.e(TAG, "Unabled to insert MMS " + values + "Uri: " + uri); 2767 return -1; 2768 } 2769 /* As we already have all the values we need, we could skip the query, but 2770 doing the query ensures we get any changes made by the content provider 2771 at insert. */ 2772 Cursor c = mResolver.query(uri, MMS_PROJECTION_SHORT, null, null, null); 2773 try { 2774 if (c != null && c.moveToFirst()) { 2775 long id = c.getLong(c.getColumnIndex(Mms._ID)); 2776 int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 2777 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 2778 int readStatus = c.getInt(c.getColumnIndex(Mms.READ)); 2779 2780 /* We must filter out any actions made by the MCE. Add the new message to 2781 * the list of known messages. */ 2782 2783 Msg newMsg = new Msg(id, type, threadId, readStatus); 2784 newMsg.localInitiatedSend = true; 2785 getMsgListMms().put(id, newMsg); 2786 c.close(); 2787 } 2788 } finally { 2789 if (c != null) c.close(); 2790 } 2791 } // Done adding changes, unlock access to mMsgListMms to allow sending MMS events again 2792 2793 long handle = Long.parseLong(uri.getLastPathSegment()); 2794 if (V) Log.v(TAG, " NEW URI " + uri.toString()); 2795 2796 try { 2797 if(msg.getMimeParts() == null) { 2798 /* Perhaps this message have been deleted, and no longer have any content, 2799 * but only headers */ 2800 Log.w(TAG, "No MMS parts present..."); 2801 } else { 2802 if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size() 2803 + " parts to the data base."); 2804 for(MimePart part : msg.getMimeParts()) { 2805 int count = 0; 2806 count++; 2807 values.clear(); 2808 if(part.mContentType != null && 2809 part.mContentType.toUpperCase().contains("TEXT")) { 2810 values.put(Mms.Part.CONTENT_TYPE, "text/plain"); 2811 values.put(Mms.Part.CHARSET, 106); 2812 if(part.mPartName != null) { 2813 values.put(Mms.Part.FILENAME, part.mPartName); 2814 values.put(Mms.Part.NAME, part.mPartName); 2815 } else { 2816 values.put(Mms.Part.FILENAME, "text_" + count +".txt"); 2817 values.put(Mms.Part.NAME, "text_" + count +".txt"); 2818 } 2819 // Ensure we have "ci" set 2820 if(part.mContentId != null) { 2821 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2822 } else { 2823 if(part.mPartName != null) { 2824 values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">"); 2825 } else { 2826 values.put(Mms.Part.CONTENT_ID, "<text_" + count + ">"); 2827 } 2828 } 2829 // Ensure we have "cl" set 2830 if(part.mContentLocation != null) { 2831 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2832 } else { 2833 if(part.mPartName != null) { 2834 values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".txt"); 2835 } else { 2836 values.put(Mms.Part.CONTENT_LOCATION, "text_" + count + ".txt"); 2837 } 2838 } 2839 2840 if(part.mContentDisposition != null) { 2841 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2842 } 2843 values.put(Mms.Part.TEXT, part.getDataAsString()); 2844 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part"); 2845 uri = mResolver.insert(uri, values); 2846 if(V) Log.v(TAG, "Added TEXT part"); 2847 2848 } else if (part.mContentType != null && 2849 part.mContentType.toUpperCase().contains("SMIL")){ 2850 values.put(Mms.Part.SEQ, -1); 2851 values.put(Mms.Part.CONTENT_TYPE, "application/smil"); 2852 if(part.mContentId != null) { 2853 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2854 } else { 2855 values.put(Mms.Part.CONTENT_ID, "<smil_" + count + ">"); 2856 } 2857 if(part.mContentLocation != null) { 2858 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2859 } else { 2860 values.put(Mms.Part.CONTENT_LOCATION, "smil_" + count + ".xml"); 2861 } 2862 2863 if(part.mContentDisposition != null) 2864 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2865 values.put(Mms.Part.FILENAME, "smil.xml"); 2866 values.put(Mms.Part.NAME, "smil.xml"); 2867 values.put(Mms.Part.TEXT, new String(part.mData, "UTF-8")); 2868 2869 uri = Uri.parse(Mms.CONTENT_URI+ "/" + handle + "/part"); 2870 uri = mResolver.insert(uri, values); 2871 if (V) Log.v(TAG, "Added SMIL part"); 2872 2873 }else /*VIDEO/AUDIO/IMAGE*/ { 2874 writeMmsDataPart(handle, part, count); 2875 if (V) Log.v(TAG, "Added OTHER part"); 2876 } 2877 if (uri != null){ 2878 if (V) Log.v(TAG, "Added part with content-type: " + part.mContentType 2879 + " to Uri: " + uri.toString()); 2880 } 2881 } 2882 } 2883 } catch (UnsupportedEncodingException e) { 2884 Log.w(TAG, e); 2885 } catch (IOException e) { 2886 Log.w(TAG, e); 2887 } 2888 2889 values.clear(); 2890 values.put(Mms.Addr.CONTACT_ID, "null"); 2891 values.put(Mms.Addr.ADDRESS, "insert-address-token"); 2892 values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_FROM); 2893 values.put(Mms.Addr.CHARSET, 106); 2894 2895 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr"); 2896 uri = mResolver.insert(uri, values); 2897 if (uri != null && V){ 2898 Log.v(TAG, " NEW URI " + uri.toString()); 2899 } 2900 2901 values.clear(); 2902 values.put(Mms.Addr.CONTACT_ID, "null"); 2903 values.put(Mms.Addr.ADDRESS, to_address); 2904 values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_TO); 2905 values.put(Mms.Addr.CHARSET, 106); 2906 2907 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr"); 2908 uri = mResolver.insert(uri, values); 2909 if (uri != null && V){ 2910 Log.v(TAG, " NEW URI " + uri.toString()); 2911 } 2912 return handle; 2913 } 2914 2915 writeMmsDataPart(long handle, MimePart part, int count)2916 private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException{ 2917 ContentValues values = new ContentValues(); 2918 values.put(Mms.Part.MSG_ID, handle); 2919 if(part.mContentType != null) { 2920 values.put(Mms.Part.CONTENT_TYPE, part.mContentType); 2921 } else { 2922 Log.w(TAG, "MMS has no CONTENT_TYPE for part " + count); 2923 } 2924 if(part.mContentId != null) { 2925 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2926 } else { 2927 if(part.mPartName != null) { 2928 values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">"); 2929 } else { 2930 values.put(Mms.Part.CONTENT_ID, "<part_" + count + ">"); 2931 } 2932 } 2933 2934 if(part.mContentLocation != null) { 2935 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2936 } else { 2937 if(part.mPartName != null) { 2938 values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".dat"); 2939 } else { 2940 values.put(Mms.Part.CONTENT_LOCATION, "part_" + count + ".dat"); 2941 } 2942 } 2943 if(part.mContentDisposition != null) 2944 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2945 if(part.mPartName != null) { 2946 values.put(Mms.Part.FILENAME, part.mPartName); 2947 values.put(Mms.Part.NAME, part.mPartName); 2948 } else { 2949 /* We must set at least one part identifier */ 2950 values.put(Mms.Part.FILENAME, "part_" + count + ".dat"); 2951 values.put(Mms.Part.NAME, "part_" + count + ".dat"); 2952 } 2953 Uri partUri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part"); 2954 Uri res = mResolver.insert(partUri, values); 2955 2956 // Add data to part 2957 OutputStream os = mResolver.openOutputStream(res); 2958 os.write(part.mData); 2959 os.close(); 2960 } 2961 2962 sendMessage(PushMsgInfo msgInfo, String msgBody)2963 public void sendMessage(PushMsgInfo msgInfo, String msgBody) { 2964 2965 SmsManager smsMng = SmsManager.getDefault(); 2966 ArrayList<String> parts = smsMng.divideMessage(msgBody); 2967 msgInfo.parts = parts.size(); 2968 // We add a time stamp to differentiate delivery reports from each other for resent messages 2969 msgInfo.timestamp = Calendar.getInstance().getTime().getTime(); 2970 msgInfo.partsDelivered = 0; 2971 msgInfo.partsSent = 0; 2972 2973 ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(msgInfo.parts); 2974 ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(msgInfo.parts); 2975 2976 /* We handle the SENT intent in the MAP service, as this object 2977 * is destroyed at disconnect, hence if a disconnect occur while sending 2978 * a message, there is no intent handler to move the message from outbox 2979 * to the correct folder. 2980 * The correct solution would be to create a service that will start based on 2981 * the intent, if BT is turned off. */ 2982 2983 if (parts != null && parts.size() > 0) { 2984 for (int i = 0; i < msgInfo.parts; i++) { 2985 Intent intentDelivery, intentSent; 2986 2987 intentDelivery = new Intent(ACTION_MESSAGE_DELIVERY, null); 2988 /* Add msgId and part number to ensure the intents are different, and we 2989 * thereby get an intent for each msg part. 2990 * setType is needed to create different intents for each message id/ time stamp, 2991 * as the extras are not used when comparing. */ 2992 intentDelivery.setType("message/" + Long.toString(msgInfo.id) + 2993 msgInfo.timestamp + i); 2994 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id); 2995 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, msgInfo.timestamp); 2996 PendingIntent pendingIntentDelivery = PendingIntent.getBroadcast(mContext, 0, 2997 intentDelivery, PendingIntent.FLAG_UPDATE_CURRENT); 2998 2999 intentSent = new Intent(ACTION_MESSAGE_SENT, null); 3000 /* Add msgId and part number to ensure the intents are different, and we 3001 * thereby get an intent for each msg part. 3002 * setType is needed to create different intents for each message id/ time stamp, 3003 * as the extras are not used when comparing. */ 3004 intentSent.setType("message/" + Long.toString(msgInfo.id) + msgInfo.timestamp + i); 3005 intentSent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id); 3006 intentSent.putExtra(EXTRA_MESSAGE_SENT_URI, msgInfo.uri.toString()); 3007 intentSent.putExtra(EXTRA_MESSAGE_SENT_RETRY, msgInfo.retry); 3008 intentSent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, msgInfo.transparent); 3009 3010 PendingIntent pendingIntentSent = PendingIntent.getBroadcast(mContext, 0, 3011 intentSent, PendingIntent.FLAG_UPDATE_CURRENT); 3012 3013 // We use the same pending intent for all parts, but do not set the one shot flag. 3014 deliveryIntents.add(pendingIntentDelivery); 3015 sentIntents.add(pendingIntentSent); 3016 } 3017 3018 Log.d(TAG, "sendMessage to " + msgInfo.phone); 3019 3020 smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents, 3021 deliveryIntents); 3022 } 3023 } 3024 3025 private class SmsBroadcastReceiver extends BroadcastReceiver { 3026 private final String[] ID_PROJECTION = new String[] { Sms._ID }; 3027 private final Uri UPDATE_STATUS_URI = Uri.withAppendedPath(Sms.CONTENT_URI, "/status"); 3028 register()3029 public void register() { 3030 Handler handler = new Handler(Looper.getMainLooper()); 3031 3032 IntentFilter intentFilter = new IntentFilter(); 3033 intentFilter.addAction(ACTION_MESSAGE_DELIVERY); 3034 try{ 3035 intentFilter.addDataType("message/*"); 3036 } catch (MalformedMimeTypeException e) { 3037 Log.e(TAG, "Wrong mime type!!!", e); 3038 } 3039 3040 mContext.registerReceiver(this, intentFilter, null, handler); 3041 } 3042 unregister()3043 public void unregister() { 3044 try { 3045 mContext.unregisterReceiver(this); 3046 } catch (IllegalArgumentException e) { 3047 /* do nothing */ 3048 } 3049 } 3050 3051 @Override onReceive(Context context, Intent intent)3052 public void onReceive(Context context, Intent intent) { 3053 String action = intent.getAction(); 3054 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3055 PushMsgInfo msgInfo = mPushMsgList.get(handle); 3056 3057 Log.d(TAG, "onReceive: action" + action); 3058 3059 if (msgInfo == null) { 3060 Log.d(TAG, "onReceive: no msgInfo found for handle " + handle); 3061 return; 3062 } 3063 3064 if (action.equals(ACTION_MESSAGE_SENT)) { 3065 int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, 3066 Activity.RESULT_CANCELED); 3067 msgInfo.partsSent++; 3068 if(result != Activity.RESULT_OK) { 3069 /* If just one of the parts in the message fails, we need to send the 3070 * entire message again 3071 */ 3072 msgInfo.failedSent = true; 3073 } 3074 if(D) Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent 3075 + ", msgInfo.parts = " + msgInfo.parts + " result = " + result); 3076 3077 if (msgInfo.partsSent == msgInfo.parts) { 3078 actionMessageSent(context, intent, msgInfo); 3079 } 3080 } else if (action.equals(ACTION_MESSAGE_DELIVERY)) { 3081 long timestamp = intent.getLongExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, 0); 3082 int status = -1; 3083 if(msgInfo.timestamp == timestamp) { 3084 msgInfo.partsDelivered++; 3085 byte[] pdu = intent.getByteArrayExtra("pdu"); 3086 String format = intent.getStringExtra("format"); 3087 3088 SmsMessage message = SmsMessage.createFromPdu(pdu, format); 3089 if (message == null) { 3090 Log.d(TAG, "actionMessageDelivery: Can't get message from pdu"); 3091 return; 3092 } 3093 status = message.getStatus(); 3094 if(status != 0/*0 is success*/) { 3095 msgInfo.statusDelivered = status; 3096 if(D) Log.d(TAG, "msgInfo.statusDelivered = " + status); 3097 Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED, 0); 3098 } else { 3099 Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_SENT, 0); 3100 } 3101 } 3102 if (msgInfo.partsDelivered == msgInfo.parts) { 3103 actionMessageDelivery(context, intent, msgInfo); 3104 } 3105 } else { 3106 Log.d(TAG, "onReceive: Unknown action " + action); 3107 } 3108 } 3109 actionMessageSent(Context context, Intent intent, PushMsgInfo msgInfo)3110 private void actionMessageSent(Context context, Intent intent, PushMsgInfo msgInfo) { 3111 /* As the MESSAGE_SENT intent is forwarded from the MAP service, we use the intent 3112 * to carry the result, as getResult() will not return the correct value. 3113 */ 3114 boolean delete = false; 3115 3116 if(D) Log.d(TAG,"actionMessageSent(): msgInfo.failedSent = " + msgInfo.failedSent); 3117 3118 msgInfo.sendInProgress = false; 3119 3120 if (msgInfo.failedSent == false) { 3121 if(D) Log.d(TAG, "actionMessageSent: result OK"); 3122 if (msgInfo.transparent == 0) { 3123 if (!Sms.moveMessageToFolder(context, msgInfo.uri, 3124 Sms.MESSAGE_TYPE_SENT, 0)) { 3125 Log.w(TAG, "Failed to move " + msgInfo.uri + " to SENT"); 3126 } 3127 } else { 3128 delete = true; 3129 } 3130 3131 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msgInfo.id, 3132 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3133 sendEvent(evt); 3134 3135 } else { 3136 if (msgInfo.retry == 1) { 3137 /* Notify failure, but keep message in outbox for resending */ 3138 msgInfo.resend = true; 3139 msgInfo.partsSent = 0; // Reset counter for the retry 3140 msgInfo.failedSent = false; 3141 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id, 3142 getSmsFolderName(Sms.MESSAGE_TYPE_OUTBOX), null, mSmsType); 3143 sendEvent(evt); 3144 } else { 3145 if (msgInfo.transparent == 0) { 3146 if (!Sms.moveMessageToFolder(context, msgInfo.uri, 3147 Sms.MESSAGE_TYPE_FAILED, 0)) { 3148 Log.w(TAG, "Failed to move " + msgInfo.uri + " to FAILED"); 3149 } 3150 } else { 3151 delete = true; 3152 } 3153 3154 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id, 3155 getSmsFolderName(Sms.MESSAGE_TYPE_FAILED), null, mSmsType); 3156 sendEvent(evt); 3157 } 3158 } 3159 3160 if (delete == true) { 3161 /* Delete from Observer message list to avoid delete notifications */ 3162 synchronized(getMsgListSms()) { 3163 getMsgListSms().remove(msgInfo.id); 3164 } 3165 3166 /* Delete from DB */ 3167 mResolver.delete(msgInfo.uri, null, null); 3168 } 3169 } 3170 actionMessageDelivery(Context context, Intent intent, PushMsgInfo msgInfo)3171 private void actionMessageDelivery(Context context, Intent intent, PushMsgInfo msgInfo) { 3172 Uri messageUri = intent.getData(); 3173 msgInfo.sendInProgress = false; 3174 3175 Cursor cursor = mResolver.query(msgInfo.uri, ID_PROJECTION, null, null, null); 3176 3177 try { 3178 if (cursor.moveToFirst()) { 3179 int messageId = cursor.getInt(0); 3180 3181 Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId); 3182 3183 if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status=" 3184 + msgInfo.statusDelivered); 3185 3186 ContentValues contentValues = new ContentValues(2); 3187 3188 contentValues.put(Sms.STATUS, msgInfo.statusDelivered); 3189 contentValues.put(Inbox.DATE_SENT, System.currentTimeMillis()); 3190 mResolver.update(updateUri, contentValues, null, null); 3191 } else { 3192 Log.d(TAG, "Can't find message for status update: " + messageUri); 3193 } 3194 } finally { 3195 if (cursor != null) cursor.close(); 3196 } 3197 3198 if (msgInfo.statusDelivered == 0) { 3199 Event evt = new Event(EVENT_TYPE_DELEVERY_SUCCESS, msgInfo.id, 3200 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3201 sendEvent(evt); 3202 } else { 3203 Event evt = new Event(EVENT_TYPE_DELIVERY_FAILURE, msgInfo.id, 3204 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3205 sendEvent(evt); 3206 } 3207 3208 mPushMsgList.remove(msgInfo.id); 3209 } 3210 } 3211 3212 private class CeBroadcastReceiver extends BroadcastReceiver { register()3213 public void register() { 3214 UserManager manager = UserManager.get(mContext); 3215 if (manager == null || manager.isUserUnlocked()) { 3216 mStorageUnlocked = true; 3217 return; 3218 } 3219 3220 Handler handler = new Handler(Looper.getMainLooper()); 3221 IntentFilter intentFilter = new IntentFilter(); 3222 intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); 3223 mContext.registerReceiver(this, intentFilter, null, handler); 3224 } 3225 unregister()3226 public void unregister() { 3227 try { 3228 mContext.unregisterReceiver(this); 3229 } catch (IllegalArgumentException e) { 3230 /* do nothing */ 3231 } 3232 } 3233 onReceive(Context context, Intent intent)3234 public void onReceive(Context context, Intent intent) { 3235 String action = intent.getAction(); 3236 Log.d(TAG, "onReceive: action" + action); 3237 3238 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 3239 try { 3240 initMsgList(); 3241 } catch (RemoteException e) { 3242 Log.e(TAG, "Error initializing SMS/MMS message lists."); 3243 } 3244 3245 for (String folder : FOLDER_SMS_MAP.values()) { 3246 Event evt = new Event(EVENT_TYPE_NEW, -1, folder, mSmsType); 3247 sendEvent(evt); 3248 } 3249 mStorageUnlocked = true; 3250 /* After unlock this BroadcastReceiver is never needed */ 3251 unregister(); 3252 } else { 3253 Log.d(TAG, "onReceive: Unknown action " + action); 3254 } 3255 } 3256 } 3257 3258 /** 3259 * Handle MMS sent intents in disconnected(MNS) state, where we do not need to send any 3260 * notifications. 3261 * @param context The context to use for provider operations 3262 * @param intent The intent received 3263 * @param result The result 3264 */ actionMmsSent(Context context, Intent intent, int result, Map<Long, Msg> mmsMsgList)3265 static public void actionMmsSent(Context context, Intent intent, int result, 3266 Map<Long, Msg> mmsMsgList) { 3267 /* 3268 * if transparent: 3269 * delete message and send notification(regardless of result) 3270 * else 3271 * Result == Success: 3272 * move to sent folder (will trigger notification) 3273 * Result == Fail: 3274 * move to outbox (send delivery fail notification) 3275 */ 3276 if(D) Log.d(TAG,"actionMmsSent()"); 3277 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3278 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3279 if(handle < 0) { 3280 Log.w(TAG, "Intent received for an invalid handle"); 3281 return; 3282 } 3283 ContentResolver resolver = context.getContentResolver(); 3284 if(transparent == 1) { 3285 /* The specification is a bit unclear about the transparent flag. If it is set 3286 * no copy of the message shall be kept in the send folder after the message 3287 * was send, but in the case of a send error, it is unclear what to do. 3288 * As it will not be transparent if we keep the message in any folder, 3289 * we delete the message regardless of the result. 3290 * If we however do have a MNS connection we need to send a notification. */ 3291 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 3292 /* Delete from observer message list to avoid delete notifications */ 3293 if(mmsMsgList != null) { 3294 synchronized(mmsMsgList) { 3295 mmsMsgList.remove(handle); 3296 } 3297 } 3298 /* Delete message */ 3299 if(D) Log.d(TAG,"Transparent in use - delete"); 3300 resolver.delete(uri, null, null); 3301 } else if (result == Activity.RESULT_OK) { 3302 /* This will trigger a notification */ 3303 moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_SENT); 3304 } else { 3305 if(mmsMsgList != null) { 3306 synchronized(mmsMsgList) { 3307 Msg msg = mmsMsgList.get(handle); 3308 if(msg != null) { 3309 msg.type=Mms.MESSAGE_BOX_OUTBOX; 3310 } 3311 } 3312 } 3313 /* Hand further retries over to the MMS application */ 3314 moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_OUTBOX); 3315 } 3316 } 3317 actionMessageSentDisconnected(Context context, Intent intent, int result)3318 static public void actionMessageSentDisconnected(Context context, Intent intent, int result) { 3319 TYPE type = TYPE.fromOrdinal( 3320 intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal())); 3321 if(type == TYPE.MMS) { 3322 actionMmsSent(context, intent, result, null); 3323 } else { 3324 actionSmsSentDisconnected(context, intent, result); 3325 } 3326 } 3327 actionSmsSentDisconnected(Context context, Intent intent, int result)3328 static public void actionSmsSentDisconnected(Context context, Intent intent, int result) { 3329 /* Check permission for message deletion. */ 3330 if ((Binder.getCallingPid() != Process.myPid()) || 3331 (context.checkCallingOrSelfPermission("android.Manifest.permission.WRITE_SMS") 3332 != PackageManager.PERMISSION_GRANTED)) { 3333 Log.w(TAG, "actionSmsSentDisconnected: Not allowed to delete SMS/MMS messages"); 3334 return; 3335 } 3336 3337 boolean delete = false; 3338 //int retry = intent.getIntExtra(EXTRA_MESSAGE_SENT_RETRY, 0); 3339 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3340 String uriString = intent.getStringExtra(EXTRA_MESSAGE_SENT_URI); 3341 if(uriString == null) { 3342 // Nothing we can do about it, just bail out 3343 return; 3344 } 3345 Uri uri = Uri.parse(uriString); 3346 3347 if (result == Activity.RESULT_OK) { 3348 Log.d(TAG, "actionMessageSentDisconnected: result OK"); 3349 if (transparent == 0) { 3350 if (!Sms.moveMessageToFolder(context, uri, 3351 Sms.MESSAGE_TYPE_SENT, 0)) { 3352 Log.d(TAG, "Failed to move " + uri + " to SENT"); 3353 } 3354 } else { 3355 delete = true; 3356 } 3357 } else { 3358 /*if (retry == 1) { 3359 The retry feature only works while connected, else we fail the send, 3360 * and move the message to failed, to let the user/app resend manually later. 3361 } else */{ 3362 if (transparent == 0) { 3363 if (!Sms.moveMessageToFolder(context, uri, 3364 Sms.MESSAGE_TYPE_FAILED, 0)) { 3365 Log.d(TAG, "Failed to move " + uri + " to FAILED"); 3366 } 3367 } else { 3368 delete = true; 3369 } 3370 } 3371 } 3372 3373 if (delete) { 3374 /* Delete from DB */ 3375 ContentResolver resolver = context.getContentResolver(); 3376 if (resolver != null) { 3377 resolver.delete(uri, null, null); 3378 } else { 3379 Log.w(TAG, "Unable to get resolver"); 3380 } 3381 } 3382 } 3383 registerPhoneServiceStateListener()3384 private void registerPhoneServiceStateListener() { 3385 TelephonyManager tm = (TelephonyManager)mContext.getSystemService( 3386 Context.TELEPHONY_SERVICE); 3387 tm.listen(mPhoneListener, PhoneStateListener.LISTEN_SERVICE_STATE); 3388 } 3389 unRegisterPhoneServiceStateListener()3390 private void unRegisterPhoneServiceStateListener() { 3391 TelephonyManager tm = (TelephonyManager)mContext.getSystemService( 3392 Context.TELEPHONY_SERVICE); 3393 tm.listen(mPhoneListener, PhoneStateListener.LISTEN_NONE); 3394 } 3395 resendPendingMessages()3396 private void resendPendingMessages() { 3397 /* Send pending messages in outbox */ 3398 String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX; 3399 UserManager manager = UserManager.get(mContext); 3400 if (manager == null || !manager.isUserUnlocked()) return; 3401 Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, 3402 null); 3403 try { 3404 if (c != null && c.moveToFirst()) { 3405 do { 3406 long id = c.getLong(c.getColumnIndex(Sms._ID)); 3407 String msgBody = c.getString(c.getColumnIndex(Sms.BODY)); 3408 PushMsgInfo msgInfo = mPushMsgList.get(id); 3409 if (msgInfo == null || msgInfo.resend == false || 3410 msgInfo.sendInProgress == true) { 3411 continue; 3412 } 3413 msgInfo.sendInProgress = true; 3414 sendMessage(msgInfo, msgBody); 3415 } while (c.moveToNext()); 3416 } 3417 } finally { 3418 if (c != null) c.close(); 3419 } 3420 3421 3422 } 3423 failPendingMessages()3424 private void failPendingMessages() { 3425 /* Move pending messages from outbox to failed */ 3426 String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX; 3427 Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, 3428 null); 3429 try { 3430 if (c != null && c.moveToFirst()) { 3431 do { 3432 long id = c.getLong(c.getColumnIndex(Sms._ID)); 3433 String msgBody = c.getString(c.getColumnIndex(Sms.BODY)); 3434 PushMsgInfo msgInfo = mPushMsgList.get(id); 3435 if (msgInfo == null || msgInfo.resend == false) { 3436 continue; 3437 } 3438 Sms.moveMessageToFolder(mContext, msgInfo.uri, 3439 Sms.MESSAGE_TYPE_FAILED, 0); 3440 } while (c.moveToNext()); 3441 } 3442 } finally { 3443 if (c != null) c.close(); 3444 } 3445 3446 } 3447 removeDeletedMessages()3448 private void removeDeletedMessages() { 3449 /* Remove messages from virtual "deleted" folder (thread_id -1) */ 3450 mResolver.delete(Sms.CONTENT_URI, 3451 "thread_id = " + DELETED_THREAD_ID, null); 3452 } 3453 3454 private PhoneStateListener mPhoneListener = new PhoneStateListener() { 3455 @Override 3456 public void onServiceStateChanged(ServiceState serviceState) { 3457 Log.d(TAG, "Phone service state change: " + serviceState.getState()); 3458 if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) { 3459 resendPendingMessages(); 3460 } 3461 } 3462 }; 3463 init()3464 public void init() { 3465 if (mSmsBroadcastReceiver != null) { 3466 mSmsBroadcastReceiver.register(); 3467 } 3468 3469 if (mCeBroadcastReceiver != null) { 3470 mCeBroadcastReceiver.register(); 3471 } 3472 3473 registerPhoneServiceStateListener(); 3474 mInitialized = true; 3475 } 3476 deinit()3477 public void deinit() { 3478 mInitialized = false; 3479 unregisterObserver(); 3480 if (mSmsBroadcastReceiver != null) { 3481 mSmsBroadcastReceiver.unregister(); 3482 } 3483 unRegisterPhoneServiceStateListener(); 3484 failPendingMessages(); 3485 removeDeletedMessages(); 3486 } 3487 handleSmsSendIntent(Context context, Intent intent)3488 public boolean handleSmsSendIntent(Context context, Intent intent){ 3489 TYPE type = TYPE.fromOrdinal( 3490 intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal())); 3491 if(type == TYPE.MMS) { 3492 return handleMmsSendIntent(context, intent); 3493 } else { 3494 if(mInitialized) { 3495 mSmsBroadcastReceiver.onReceive(context, intent); 3496 return true; 3497 } 3498 } 3499 return false; 3500 } 3501 handleMmsSendIntent(Context context, Intent intent)3502 public boolean handleMmsSendIntent(Context context, Intent intent){ 3503 if(D) Log.w(TAG, "handleMmsSendIntent()"); 3504 if(mMnsClient.isConnected() == false) { 3505 // No need to handle notifications, just use default handling 3506 if(D) Log.w(TAG, "MNS not connected - use static handling"); 3507 return false; 3508 } 3509 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3510 int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED); 3511 actionMmsSent(context, intent, result, getMsgListMms()); 3512 if(handle < 0) { 3513 Log.w(TAG, "Intent received for an invalid handle"); 3514 return true; 3515 } 3516 if(result != Activity.RESULT_OK) { 3517 if(mObserverRegistered) { 3518 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, handle, 3519 getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS); 3520 sendEvent(evt); 3521 } 3522 } else { 3523 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3524 if(transparent != 0) { 3525 if(mObserverRegistered) { 3526 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, handle, 3527 getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS); 3528 sendEvent(evt); 3529 } 3530 } 3531 } 3532 return true; 3533 } 3534 3535 } 3536