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