BluetoothMapContentObserver.java revision d04c0a8609b39f891d5787f051b1dfb105ceee93
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 */ 15package com.android.bluetooth.map; 16 17import android.annotation.TargetApi; 18import android.app.Activity; 19import android.app.PendingIntent; 20import android.content.BroadcastReceiver; 21import android.content.ContentProviderClient; 22import android.content.ContentResolver; 23import android.content.ContentUris; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.IntentFilter.MalformedMimeTypeException; 29import android.content.pm.PackageManager; 30import android.database.ContentObserver; 31import android.database.Cursor; 32import android.database.sqlite.SQLiteException; 33import android.net.Uri; 34import android.os.Binder; 35import android.os.Handler; 36import android.os.Looper; 37import android.os.Message; 38import android.os.ParcelFileDescriptor; 39import android.os.Process; 40import android.os.RemoteException; 41import android.os.UserManager; 42import android.provider.Telephony; 43import android.provider.Telephony.Mms; 44import android.provider.Telephony.MmsSms; 45import android.provider.Telephony.Sms; 46import android.provider.Telephony.Sms.Inbox; 47import android.telephony.PhoneStateListener; 48import android.telephony.ServiceState; 49import android.telephony.SmsManager; 50import android.telephony.TelephonyManager; 51import android.text.format.DateUtils; 52import android.text.TextUtils; 53import android.util.Log; 54import android.util.Xml; 55 56import org.xmlpull.v1.XmlSerializer; 57 58import com.android.bluetooth.map.BluetoothMapUtils.TYPE; 59import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart; 60import com.android.bluetooth.mapapi.BluetoothMapContract; 61import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns; 62import com.google.android.mms.pdu.PduHeaders; 63 64import java.io.FileNotFoundException; 65import java.io.FileOutputStream; 66import java.io.IOException; 67import java.io.OutputStream; 68import java.io.StringWriter; 69import java.io.UnsupportedEncodingException; 70import java.util.ArrayList; 71import java.util.Arrays; 72import java.util.Calendar; 73import java.util.Collections; 74import java.util.HashMap; 75import java.util.HashSet; 76import java.util.Map; 77import java.util.Objects; 78import java.util.Set; 79 80import javax.obex.ResponseCodes; 81 82@TargetApi(19) 83public 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 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 setObserverRemoteFeatureMask(mMasInstance.getRemoteFeatureMask()); 271 272 if(account != null) { 273 mAuthority = Uri.parse(account.mBase_uri).getAuthority(); 274 mMessageUri = Uri.parse(account.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE); 275 if (mAccount.getType() == TYPE.IM) { 276 mContactUri = Uri.parse(account.mBase_uri + "/" 277 + BluetoothMapContract.TABLE_CONVOCONTACT); 278 } 279 // TODO: We need to release this again! 280 mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority); 281 if (mProviderClient == null) { 282 throw new RemoteException("Failed to acquire provider for " + mAuthority); 283 } 284 mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT); 285 mContactList = mMasInstance.getContactList(); 286 if(mContactList == null) { 287 setContactList(new HashMap<String, BluetoothMapConvoContactElement>(), false); 288 initContactsList(); 289 } 290 } 291 mEnableSmsMms = enableSmsMms; 292 mSmsType = getSmsType(); 293 mMnsClient = mnsClient; 294 /* Get the cached list - if any, else create */ 295 mMsgListSms = mMasInstance.getMsgListSms(); 296 boolean doInit = false; 297 if(mEnableSmsMms) { 298 if(mMsgListSms == null) { 299 setMsgListSms(new HashMap<Long, Msg>(), false); 300 doInit = true; 301 } 302 mMsgListMms = mMasInstance.getMsgListMms(); 303 if(mMsgListMms == null) { 304 setMsgListMms(new HashMap<Long, Msg>(), false); 305 doInit = true; 306 } 307 } 308 if(mAccount != null) { 309 mMsgListMsg = mMasInstance.getMsgListMsg(); 310 if(mMsgListMsg == null) { 311 setMsgListMsg(new HashMap<Long, Msg>(), false); 312 doInit = true; 313 } 314 } 315 if(doInit) { 316 initMsgList(); 317 } 318 } 319 320 public int getObserverRemoteFeatureMask() { 321 if (V) Log.v(TAG, "getObserverRemoteFeatureMask : " + mMapEventReportVersion 322 + " mMapSupportedFeatures: " + mMapSupportedFeatures); 323 return mMapSupportedFeatures; 324 } 325 326 public void setObserverRemoteFeatureMask(int remoteSupportedFeatures) { 327 mMapSupportedFeatures = 328 remoteSupportedFeatures & BluetoothMapMasInstance.SDP_MAP_MAS_FEATURES; 329 if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT 330 & mMapSupportedFeatures) != 0) { 331 mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11; 332 } 333 // Make sure support for all formats result in latest version returned 334 if ((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT 335 & mMapSupportedFeatures) != 0) { 336 mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12; 337 } else if (((BluetoothMapUtils.MAP_FEATURE_PARTICIPANT_PRESENCE_CHANGE_BIT 338 | BluetoothMapUtils.MAP_FEATURE_PARTICIPANT_CHAT_STATE_CHANGE_BIT) 339 & mMapSupportedFeatures) 340 != 0) { 341 // Warning according to page 46/123 of MAP 1.3 spec 342 Log.w(TAG, 343 "setObserverRemoteFeatureMask: Extended Event Reports 1.2 is not set even" 344 + "though PARTICIPANT_PRESENCE_CHANGE_BIT or PARTICIPANT_CHAT_STATE_CHANGE_BIT" 345 + " were set, mMapSupportedFeatures=" + mMapSupportedFeatures); 346 } 347 if (D) 348 Log.d(TAG, 349 "setObserverRemoteFeatureMask: mMapEventReportVersion=" + mMapEventReportVersion 350 + " mMapSupportedFeatures=" + mMapSupportedFeatures); 351 } 352 353 private Map<Long, Msg> getMsgListSms() { 354 return mMsgListSms; 355 } 356 357 private void setMsgListSms(Map<Long, Msg> msgListSms, boolean changesDetected) { 358 mMsgListSms = msgListSms; 359 if(changesDetected) { 360 mMasInstance.updateFolderVersionCounter(); 361 } 362 mMasInstance.setMsgListSms(msgListSms); 363 } 364 365 366 private Map<Long, Msg> getMsgListMms() { 367 return mMsgListMms; 368 } 369 370 371 private void setMsgListMms(Map<Long, Msg> msgListMms, boolean changesDetected) { 372 mMsgListMms = msgListMms; 373 if(changesDetected) { 374 mMasInstance.updateFolderVersionCounter(); 375 } 376 mMasInstance.setMsgListMms(msgListMms); 377 } 378 379 380 private Map<Long, Msg> getMsgListMsg() { 381 return mMsgListMsg; 382 } 383 384 385 private void setMsgListMsg(Map<Long, Msg> msgListMsg, boolean changesDetected) { 386 mMsgListMsg = msgListMsg; 387 if(changesDetected) { 388 mMasInstance.updateFolderVersionCounter(); 389 } 390 mMasInstance.setMsgListMsg(msgListMsg); 391 } 392 393 private Map<String, BluetoothMapConvoContactElement> getContactList() { 394 return mContactList; 395 } 396 397 398 /** 399 * Currently we only have data for IM / email contacts 400 * @param contactList 401 * @param changesDetected that is not chat state changed nor presence state changed. 402 */ 403 private void setContactList(Map<String, BluetoothMapConvoContactElement> contactList, 404 boolean changesDetected) { 405 mContactList = contactList; 406 if(changesDetected) { 407 mMasInstance.updateImEmailConvoListVersionCounter(); 408 } 409 mMasInstance.setContactList(contactList); 410 } 411 412 private static boolean sendEventNewMessage(long eventFilter) { 413 return ((eventFilter & EVENT_FILTER_NEW_MESSAGE) > 0); 414 } 415 416 private static boolean sendEventMessageDeleted(long eventFilter) { 417 return ((eventFilter & EVENT_FILTER_MESSAGE_DELETED) > 0); 418 } 419 420 private static boolean sendEventMessageShift(long eventFilter) { 421 return ((eventFilter & EVENT_FILTER_MESSAGE_SHIFT) > 0); 422 } 423 424 private static boolean sendEventSendingSuccess(long eventFilter) { 425 return ((eventFilter & EVENT_FILTER_SENDING_SUCCESS) > 0); 426 } 427 428 private static boolean sendEventSendingFailed(long eventFilter) { 429 return ((eventFilter & EVENT_FILTER_SENDING_FAILED) > 0); 430 } 431 432 private static boolean sendEventDeliverySuccess(long eventFilter) { 433 return ((eventFilter & EVENT_FILTER_DELIVERY_SUCCESS) > 0); 434 } 435 436 private static boolean sendEventDeliveryFailed(long eventFilter) { 437 return ((eventFilter & EVENT_FILTER_DELIVERY_FAILED) > 0); 438 } 439 440 private static boolean sendEventReadStatusChanged(long eventFilter) { 441 return ((eventFilter & EVENT_FILTER_READ_STATUS_CHANGED) > 0); 442 } 443 444 private static boolean sendEventConversationChanged(long eventFilter) { 445 return ((eventFilter & EVENT_FILTER_CONVERSATION_CHANGED) > 0); 446 } 447 448 private static boolean sendEventParticipantPresenceChanged(long eventFilter) { 449 return ((eventFilter & EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED) > 0); 450 } 451 452 private static boolean sendEventParticipantChatstateChanged(long eventFilter) { 453 return ((eventFilter & EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED) > 0); 454 } 455 456 private static boolean sendEventMessageRemoved(long eventFilter) { 457 return ((eventFilter & EVENT_FILTER_MESSAGE_REMOVED) > 0); 458 } 459 460 private TYPE getSmsType() { 461 TYPE smsType = null; 462 TelephonyManager tm = (TelephonyManager) mContext.getSystemService( 463 Context.TELEPHONY_SERVICE); 464 465 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 466 smsType = TYPE.SMS_CDMA; 467 } else { 468 smsType = TYPE.SMS_GSM; 469 } 470 471 return smsType; 472 } 473 474 private final ContentObserver mObserver = new ContentObserver(new Handler()) { 475 @Override 476 public void onChange(boolean selfChange) { 477 onChange(selfChange, null); 478 } 479 480 @Override 481 public void onChange(boolean selfChange, Uri uri) { 482 if(uri == null) { 483 Log.w(TAG, "onChange() with URI == null - not handled."); 484 return; 485 } 486 487 if (!mStorageUnlocked) { 488 Log.v(TAG, "Ignore events until storage is completely unlocked"); 489 return; 490 } 491 492 if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId() 493 + " Uri: " + uri.toString() + " selfchange: " + selfChange); 494 495 if(uri.toString().contains(BluetoothMapContract.TABLE_CONVOCONTACT)) 496 handleContactListChanges(uri); 497 else 498 handleMsgListChanges(uri); 499 } 500 }; 501 502 private static final HashMap<Integer, String> FOLDER_SMS_MAP; 503 static { 504 FOLDER_SMS_MAP = new HashMap<Integer, String>(); 505 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX); 506 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT, BluetoothMapContract.FOLDER_NAME_SENT); 507 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT, BluetoothMapContract.FOLDER_NAME_DRAFT); 508 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX); 509 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED, BluetoothMapContract.FOLDER_NAME_OUTBOX); 510 FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED, BluetoothMapContract.FOLDER_NAME_OUTBOX); 511 } 512 513 private static String getSmsFolderName(int type) { 514 String name = FOLDER_SMS_MAP.get(type); 515 if(name != null) { 516 return name; 517 } 518 Log.e(TAG, "New SMS mailbox types have been introduced, without an update in BT..."); 519 return "Unknown"; 520 } 521 522 523 private static final HashMap<Integer, String> FOLDER_MMS_MAP; 524 static { 525 FOLDER_MMS_MAP = new HashMap<Integer, String>(); 526 FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX); 527 FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT, BluetoothMapContract.FOLDER_NAME_SENT); 528 FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_DRAFTS, BluetoothMapContract.FOLDER_NAME_DRAFT); 529 FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX); 530 } 531 532 private static String getMmsFolderName(int mailbox) { 533 String name = FOLDER_MMS_MAP.get(mailbox); 534 if(name != null) { 535 return name; 536 } 537 Log.e(TAG, "New MMS mailboxes have been introduced, without an update in BT..."); 538 return "Unknown"; 539 } 540 541 /** 542 * Set the folder structure to be used for this instance. 543 * @param folderStructure 544 */ 545 public void setFolderStructure(BluetoothMapFolderElement folderStructure) { 546 this.mFolders = folderStructure; 547 } 548 549 private class ConvoContactInfo { 550 public int mConvoColConvoId = -1; 551 public int mConvoColLastActivity = -1; 552 public int mConvoColName = -1; 553 // public int mConvoColRead = -1; 554 // public int mConvoColVersionCounter = -1; 555 public int mContactColUci = -1; 556 public int mContactColConvoId = -1; 557 public int mContactColName = -1; 558 public int mContactColNickname = -1; 559 public int mContactColBtUid = -1; 560 public int mContactColChatState = -1; 561 public int mContactColContactId = -1; 562 public int mContactColLastActive = -1; 563 public int mContactColPresenceState = -1; 564 public int mContactColPresenceText = -1; 565 public int mContactColPriority = -1; 566 public int mContactColLastOnline = -1; 567 568 public void setConvoColunms(Cursor c) { 569 // mConvoColConvoId = c.getColumnIndex( 570 // BluetoothMapContract.ConversationColumns.THREAD_ID); 571 // mConvoColLastActivity = c.getColumnIndex( 572 // BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY); 573 // mConvoColName = c.getColumnIndex( 574 // BluetoothMapContract.ConversationColumns.THREAD_NAME); 575 mContactColConvoId = c.getColumnIndex( 576 BluetoothMapContract.ConvoContactColumns.CONVO_ID); 577 mContactColName = c.getColumnIndex( 578 BluetoothMapContract.ConvoContactColumns.NAME); 579 mContactColNickname = c.getColumnIndex( 580 BluetoothMapContract.ConvoContactColumns.NICKNAME); 581 mContactColBtUid = c.getColumnIndex( 582 BluetoothMapContract.ConvoContactColumns.X_BT_UID); 583 mContactColChatState = c.getColumnIndex( 584 BluetoothMapContract.ConvoContactColumns.CHAT_STATE); 585 mContactColUci = c.getColumnIndex( 586 BluetoothMapContract.ConvoContactColumns.UCI); 587 mContactColNickname = c.getColumnIndex( 588 BluetoothMapContract.ConvoContactColumns.NICKNAME); 589 mContactColLastActive = c.getColumnIndex( 590 BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE); 591 mContactColName = c.getColumnIndex( 592 BluetoothMapContract.ConvoContactColumns.NAME); 593 mContactColPresenceState = c.getColumnIndex( 594 BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE); 595 mContactColPresenceText = c.getColumnIndex( 596 BluetoothMapContract.ConvoContactColumns.STATUS_TEXT); 597 mContactColPriority = c.getColumnIndex( 598 BluetoothMapContract.ConvoContactColumns.PRIORITY); 599 mContactColLastOnline = c.getColumnIndex( 600 BluetoothMapContract.ConvoContactColumns.LAST_ONLINE); 601 } 602 } 603 604 private class Event { 605 String eventType; 606 long handle; 607 String folder = null; 608 String oldFolder = null; 609 TYPE msgType; 610 /* Extended event parameters in MAP Event version 1.1 */ 611 String datetime = null; // OBEX time "YYYYMMDDTHHMMSS" 612 String uci = null; 613 String subject = null; 614 String senderName = null; 615 String priority = null; 616 /* Event parameters in MAP Event version 1.2 */ 617 String conversationName = null; 618 long conversationID = -1; 619 int presenceState = BluetoothMapContract.PresenceState.UNKNOWN; 620 String presenceStatus = null; 621 int chatState = BluetoothMapContract.ChatState.UNKNOWN; 622 623 static final String PATH = "telecom/msg/"; 624 625 private void setFolderPath(String name, TYPE type) { 626 if (name != null) { 627 if(type == TYPE.EMAIL || type == TYPE.IM) { 628 this.folder = name; 629 } else { 630 this.folder = PATH + name; 631 } 632 } else { 633 this.folder = null; 634 } 635 } 636 637 Event(String eventType, long handle, String folder, 638 String oldFolder, TYPE msgType) { 639 this.eventType = eventType; 640 this.handle = handle; 641 setFolderPath(folder, msgType); 642 if (oldFolder != null) { 643 if(msgType == TYPE.EMAIL || msgType == TYPE.IM) { 644 this.oldFolder = oldFolder; 645 } else { 646 this.oldFolder = PATH + oldFolder; 647 } 648 } else { 649 this.oldFolder = null; 650 } 651 this.msgType = msgType; 652 } 653 654 Event(String eventType, long handle, String folder, TYPE msgType) { 655 this.eventType = eventType; 656 this.handle = handle; 657 setFolderPath(folder, msgType); 658 this.msgType = msgType; 659 } 660 661 /* extended event type 1.1 */ 662 Event(String eventType, long handle, String folder, TYPE msgType, 663 String datetime, String subject, String senderName, String priority) { 664 this.eventType = eventType; 665 this.handle = handle; 666 setFolderPath(folder, msgType); 667 this.msgType = msgType; 668 this.datetime = datetime; 669 if (subject != null) { 670 this.subject = BluetoothMapUtils.stripInvalidChars(subject); 671 } 672 if (senderName != null) { 673 this.senderName = BluetoothMapUtils.stripInvalidChars(senderName); 674 } 675 this.priority = priority; 676 } 677 678 /* extended event type 1.2 message events */ 679 Event(String eventType, long handle, String folder, TYPE msgType, 680 String datetime, String subject, String senderName, String priority, 681 long conversationID, String conversationName) { 682 this.eventType = eventType; 683 this.handle = handle; 684 setFolderPath(folder, msgType); 685 this.msgType = msgType; 686 this.datetime = datetime; 687 if (subject != null) { 688 this.subject = BluetoothMapUtils.stripInvalidChars(subject); 689 } 690 if (senderName != null) { 691 this.senderName = BluetoothMapUtils.stripInvalidChars(senderName); 692 } 693 if (conversationID != 0) { 694 this.conversationID = conversationID; 695 } 696 if (conversationName != null) { 697 this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName); 698 } 699 this.priority = priority; 700 } 701 702 /* extended event type 1.2 for conversation, presence or chat state changed events */ 703 Event(String eventType, String uci, TYPE msgType, String name, String priority, 704 String lastActivity, long conversationID, String conversationName, 705 int presenceState, String presenceStatus, int chatState) { 706 this.eventType = eventType; 707 this.uci = uci; 708 this.msgType = msgType; 709 if (name != null) { 710 this.senderName = BluetoothMapUtils.stripInvalidChars(name); 711 } 712 this.priority = priority; 713 this.datetime = lastActivity; 714 if (conversationID != 0) { 715 this.conversationID = conversationID; 716 } 717 if (conversationName != null) { 718 this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName); 719 } 720 if (presenceState != BluetoothMapContract.PresenceState.UNKNOWN) { 721 this.presenceState = presenceState; 722 } 723 if (presenceStatus != null) { 724 this.presenceStatus = BluetoothMapUtils.stripInvalidChars(presenceStatus); 725 } 726 if (chatState != BluetoothMapContract.ChatState.UNKNOWN) { 727 this.chatState = chatState; 728 } 729 } 730 731 public byte[] encode() throws UnsupportedEncodingException { 732 StringWriter sw = new StringWriter(); 733 XmlSerializer xmlEvtReport = Xml.newSerializer(); 734 735 try { 736 xmlEvtReport.setOutput(sw); 737 xmlEvtReport.startDocument("UTF-8", true); 738 xmlEvtReport.text("\r\n"); 739 xmlEvtReport.startTag("", "MAP-event-report"); 740 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V12) { 741 xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V12_STR); 742 } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 743 xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V11_STR); 744 } else { 745 xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V10_STR); 746 } 747 xmlEvtReport.startTag("", "event"); 748 xmlEvtReport.attribute("", "type", eventType); 749 if (eventType.equals(EVENT_TYPE_CONVERSATION) || 750 eventType.equals(EVENT_TYPE_PRESENCE) || 751 eventType.equals(EVENT_TYPE_CHAT_STATE)) { 752 xmlEvtReport.attribute("", "participant_uci", uci); 753 } else { 754 xmlEvtReport.attribute("", "handle", 755 BluetoothMapUtils.getMapHandle(handle, msgType)); 756 } 757 758 if (folder != null) { 759 xmlEvtReport.attribute("", "folder", folder); 760 } 761 if (oldFolder != null) { 762 xmlEvtReport.attribute("", "old_folder", oldFolder); 763 } 764 /* Avoid possible NPE for "msgType" "null" value. "msgType" 765 * is a implied attribute and will be set "null" for events 766 * like "memory full" or "memory available" */ 767 if (msgType != null) { 768 xmlEvtReport.attribute("", "msg_type", msgType.name()); 769 } 770 /* If MAP event report version is above 1.0 send 771 * extended event report parameters */ 772 if (datetime != null) { 773 xmlEvtReport.attribute("", "datetime", datetime); 774 } 775 if (subject != null) { 776 xmlEvtReport.attribute("", "subject", 777 subject.substring(0,subject.length() < 256 ? subject.length() : 256)); 778 } 779 if (senderName != null) { 780 xmlEvtReport.attribute("", "sender_name", senderName); 781 } 782 if (priority != null) { 783 xmlEvtReport.attribute("", "priority", priority); 784 } 785 786 //} 787 /* Include conversation information from event version 1.2 */ 788 if (mMapEventReportVersion > BluetoothMapUtils.MAP_EVENT_REPORT_V11 ) { 789 if (conversationName != null) { 790 xmlEvtReport.attribute("", "conversation_name", conversationName); 791 } 792 if (conversationID != -1) { 793 // Convert provider conversation handle to string incl type 794 xmlEvtReport.attribute("", "conversation_id", 795 BluetoothMapUtils.getMapConvoHandle(conversationID, msgType)); 796 } 797 if (eventType.equals(EVENT_TYPE_PRESENCE)) { 798 if (presenceState != 0) { 799 // Convert provider conversation handle to string incl type 800 xmlEvtReport.attribute("", "presence_availability", 801 String.valueOf(presenceState)); 802 } 803 if (presenceStatus != null) { 804 // Convert provider conversation handle to string incl type 805 xmlEvtReport.attribute("", "presence_status", 806 presenceStatus.substring( 807 0,presenceStatus.length() < 256 ? subject.length() : 256)); 808 } 809 } 810 if (eventType.equals(EVENT_TYPE_PRESENCE)) { 811 if (chatState != 0) { 812 // Convert provider conversation handle to string incl type 813 xmlEvtReport.attribute("", "chat_state", String.valueOf(chatState)); 814 } 815 } 816 817 } 818 xmlEvtReport.endTag("", "event"); 819 xmlEvtReport.endTag("", "MAP-event-report"); 820 xmlEvtReport.endDocument(); 821 } catch (IllegalArgumentException e) { 822 if(D) Log.w(TAG,e); 823 } catch (IllegalStateException e) { 824 if(D) Log.w(TAG,e); 825 } catch (IOException e) { 826 if(D) Log.w(TAG,e); 827 } 828 829 if (V) Log.d(TAG, sw.toString()); 830 831 return sw.toString().getBytes("UTF-8"); 832 } 833 } 834 835 /*package*/ class Msg { 836 long id; 837 int type; // Used as folder for SMS/MMS 838 int threadId; // Used for SMS/MMS at delete 839 long folderId = -1; // Email folder ID 840 long oldFolderId = -1; // Used for email undelete 841 boolean localInitiatedSend = false; // Used for MMS to filter out events 842 boolean transparent = false; // Used for EMAIL to delete message sent with transparency 843 int flagRead = -1; // Message status read/unread 844 845 Msg(long id, int type, int threadId, int readFlag) { 846 this.id = id; 847 this.type = type; 848 this.threadId = threadId; 849 this.flagRead = readFlag; 850 } 851 Msg(long id, long folderId, int readFlag) { 852 this.id = id; 853 this.folderId = folderId; 854 this.flagRead = readFlag; 855 } 856 857 /* Eclipse generated hashCode() and equals() to make 858 * hashMap lookup work independent of whether the obj 859 * is used for email or SMS/MMS and whether or not the 860 * oldFolder is set. */ 861 @Override 862 public int hashCode() { 863 final int prime = 31; 864 int result = 1; 865 result = prime * result + (int) (id ^ (id >>> 32)); 866 return result; 867 } 868 869 @Override 870 public boolean equals(Object obj) { 871 if (this == obj) 872 return true; 873 if (obj == null) 874 return false; 875 if (getClass() != obj.getClass()) 876 return false; 877 Msg other = (Msg) obj; 878 if (id != other.id) 879 return false; 880 return true; 881 } 882 } 883 884 private Map<Long, Msg> mMsgListSms = null; 885 886 private Map<Long, Msg> mMsgListMms = null; 887 888 private Map<Long, Msg> mMsgListMsg = null; 889 890 private Map<String, BluetoothMapConvoContactElement> mContactList = null; 891 892 public int setNotificationRegistration(int notificationStatus) throws RemoteException { 893 // Forward the request to the MNS thread as a message - including the MAS instance ID. 894 if(D) Log.d(TAG,"setNotificationRegistration() enter"); 895 if (mMnsClient == null) { 896 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 897 } 898 Handler mns = mMnsClient.getMessageHandler(); 899 if (mns != null) { 900 Message msg = mns.obtainMessage(); 901 if (mMnsClient.isValidMnsRecord()) { 902 msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION; 903 } else { 904 //Trigger SDP Search and notificaiton registration , if SDP record not found. 905 msg.what = BluetoothMnsObexClient.MSG_MNS_SDP_SEARCH_REGISTRATION; 906 if (mMnsClient.mMnsLstRegRqst != null && 907 (mMnsClient.mMnsLstRegRqst.isSearchInProgress())) { 908 /* 1. Disallow next Notification ON Request : 909 * - Respond "Service Unavailable" as SDP Search and last notification 910 * registration ON request is already InProgress. 911 * - Next notification ON Request will be allowed ONLY after search 912 * and connect for last saved request [Replied with OK ] is processed. 913 */ 914 if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) { 915 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 916 } else { 917 /* 2. Allow next Notification OFF Request: 918 * - Keep the SDP search still in progress. 919 * - Disconnect and Deregister the contentObserver. 920 */ 921 msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION; 922 } 923 } 924 } 925 msg.arg1 = mMasId; 926 msg.arg2 = notificationStatus; 927 mns.sendMessageDelayed(msg, 10); // Send message without forcing a context switch 928 /* Some devices - e.g. PTS needs to get the unregister confirm before we actually 929 * disconnect the MNS. */ 930 if(D) Log.d(TAG,"setNotificationRegistration() send : " + msg.what + " to MNS "); 931 return ResponseCodes.OBEX_HTTP_OK; 932 } else { 933 // This should not happen except at shutdown. 934 if(D) Log.d(TAG,"setNotificationRegistration() Unable to send registration request"); 935 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 936 } 937 } 938 939 boolean eventMaskContainsContacts(long mask) { 940 return sendEventParticipantPresenceChanged(mask); 941 } 942 943 boolean eventMaskContainsCovo(long mask) { 944 return (sendEventConversationChanged(mask) 945 || sendEventParticipantChatstateChanged(mask)); 946 } 947 948 /* Overwrite the existing notification filter. Will register/deregister observers for 949 * the Contacts and Conversation table as needed. We keep the message observer 950 * at all times. */ 951 /*package*/ synchronized void setNotificationFilter(long newFilter) { 952 long oldFilter = mEventFilter; 953 mEventFilter = newFilter; 954 /* Contacts */ 955 if(!eventMaskContainsContacts(oldFilter) && 956 eventMaskContainsContacts(newFilter)) { 957 // TODO: 958 // Enable the observer 959 // Reset the contacts list 960 } 961 /* Conversations */ 962 if(!eventMaskContainsCovo(oldFilter) && 963 eventMaskContainsCovo(newFilter)) { 964 // TODO: 965 // Enable the observer 966 // Reset the conversations list 967 } 968 } 969 970 public void registerObserver() throws RemoteException{ 971 if (V) Log.d(TAG, "registerObserver"); 972 973 if (mObserverRegistered) 974 return; 975 976 if(mAccount != null) { 977 978 mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority); 979 if (mProviderClient == null) { 980 throw new RemoteException("Failed to acquire provider for " + mAuthority); 981 } 982 mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT); 983 984 // If there is a change in the database before we init the lists we will be sending 985 // loads of events - hence init before register. 986 if(mAccount.getType() == TYPE.IM) { 987 // Further add contact list tracking 988 initContactsList(); 989 } 990 } 991 // If there is a change in the database before we init the lists we will be sending 992 // loads of events - hence init before register. 993 initMsgList(); 994 995 /* Use MmsSms Uri since the Sms Uri is not notified on deletes */ 996 if(mEnableSmsMms){ 997 //this is sms/mms 998 mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver); 999 mObserverRegistered = true; 1000 } 1001 1002 if(mAccount != null) { 1003 /* For URI's without account ID */ 1004 Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/" 1005 + BluetoothMapContract.TABLE_MESSAGE); 1006 if(D) Log.d(TAG, "Registering observer for: " + uri); 1007 mResolver.registerContentObserver(uri, true, mObserver); 1008 1009 /* For URI's with account ID - is handled the same way as without ID, but is 1010 * only triggered for MAS instances with matching account ID. */ 1011 uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE); 1012 if(D) Log.d(TAG, "Registering observer for: " + uri); 1013 mResolver.registerContentObserver(uri, true, mObserver); 1014 1015 if(mAccount.getType() == TYPE.IM) { 1016 1017 uri = Uri.parse(mAccount.mBase_uri_no_account + "/" 1018 + BluetoothMapContract.TABLE_CONVOCONTACT); 1019 if(D) Log.d(TAG, "Registering observer for: " + uri); 1020 mResolver.registerContentObserver(uri, true, mObserver); 1021 1022 /* For URI's with account ID - is handled the same way as without ID, but is 1023 * only triggered for MAS instances with matching account ID. */ 1024 uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_CONVOCONTACT); 1025 if(D) Log.d(TAG, "Registering observer for: " + uri); 1026 mResolver.registerContentObserver(uri, true, mObserver); 1027 } 1028 1029 mObserverRegistered = true; 1030 } 1031 } 1032 1033 public void unregisterObserver() { 1034 if (V) Log.d(TAG, "unregisterObserver"); 1035 mResolver.unregisterContentObserver(mObserver); 1036 mObserverRegistered = false; 1037 if(mProviderClient != null){ 1038 mProviderClient.release(); 1039 mProviderClient = null; 1040 } 1041 } 1042 1043 /** 1044 * Per design it is only possible to call the refreshXxxx functions sequentially, hence it 1045 * is safe to modify mTransmitEvents without synchronization. 1046 */ 1047 /* package */ void refreshFolderVersionCounter() { 1048 if (mObserverRegistered) { 1049 // As we have observers, we already keep the counter up-to-date. 1050 return; 1051 } 1052 /* We need to perform the same functionality, as when we receive a notification change, 1053 hence we: 1054 - disable the event transmission 1055 - triggers the code for updates 1056 - enable the event transmission */ 1057 mTransmitEvents = false; 1058 try { 1059 if(mEnableSmsMms) { 1060 handleMsgListChangesSms(); 1061 handleMsgListChangesMms(); 1062 } 1063 if(mAccount != null) { 1064 try { 1065 handleMsgListChangesMsg(mMessageUri); 1066 } catch (RemoteException e) { 1067 Log.e(TAG, "Unable to update FolderVersionCounter. - Not fatal, but can cause" + 1068 " undesirable user experience!", e); 1069 } 1070 } 1071 } finally { 1072 // Ensure we always enable events again 1073 mTransmitEvents = true; 1074 } 1075 } 1076 1077 /* package */ void refreshConvoListVersionCounter() { 1078 if (mObserverRegistered) { 1079 // As we have observers, we already keep the counter up-to-date. 1080 return; 1081 } 1082 /* We need to perform the same functionality, as when we receive a notification change, 1083 hence we: 1084 - disable event transmission 1085 - triggers the code for updates 1086 - enable event transmission */ 1087 mTransmitEvents = false; 1088 try { 1089 if((mAccount != null) && (mContactUri != null)) { 1090 handleContactListChanges(mContactUri); 1091 } 1092 } finally { 1093 // Ensure we always enable events again 1094 mTransmitEvents = true; 1095 } 1096 } 1097 1098 private void sendEvent(Event evt) { 1099 1100 if(!mTransmitEvents) { 1101 if(V) Log.v(TAG, "mTransmitEvents == false - don't send event."); 1102 return; 1103 } 1104 1105 if(D)Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " " + evt.folder + " " 1106 + evt.oldFolder + " " + evt.msgType.name() + " " + evt.datetime + " " 1107 + evt.subject + " " + evt.senderName + " " + evt.priority ); 1108 1109 if (mMnsClient == null || !mMnsClient.isConnected()) { 1110 Log.d(TAG, "sendEvent: No MNS client registered or connected- don't send event"); 1111 return; 1112 } 1113 1114 /* Enable use of the cache for checking the filter */ 1115 long eventFilter = mEventFilter; 1116 1117 /* This should have been a switch on the string, but it is not allowed in Java 1.6 */ 1118 /* WARNING: Here we do pointer compare for the string to speed up things, that is. 1119 * HENCE: always use the EVENT_TYPE_"defines" */ 1120 if(Objects.equals(evt.eventType, EVENT_TYPE_NEW)) { 1121 if(!sendEventNewMessage(eventFilter)) { 1122 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1123 return; 1124 } 1125 } else if(Objects.equals(evt.eventType, EVENT_TYPE_DELETE)) { 1126 if(!sendEventMessageDeleted(eventFilter)) { 1127 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1128 return; 1129 } 1130 } else if(Objects.equals(evt.eventType, EVENT_TYPE_REMOVED)) { 1131 if(!sendEventMessageRemoved(eventFilter)) { 1132 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1133 return; 1134 } 1135 } else if(Objects.equals(evt.eventType, EVENT_TYPE_SHIFT)) { 1136 if(!sendEventMessageShift(eventFilter)) { 1137 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1138 return; 1139 } 1140 } else if(Objects.equals(evt.eventType, EVENT_TYPE_DELEVERY_SUCCESS)) { 1141 if(!sendEventDeliverySuccess(eventFilter)) { 1142 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1143 return; 1144 } 1145 } else if(Objects.equals(evt.eventType, EVENT_TYPE_SENDING_SUCCESS)) { 1146 if(!sendEventSendingSuccess(eventFilter)) { 1147 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1148 return; 1149 } 1150 } else if(Objects.equals(evt.eventType, EVENT_TYPE_SENDING_FAILURE)) { 1151 if(!sendEventSendingFailed(eventFilter)) { 1152 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1153 return; 1154 } 1155 } else if(Objects.equals(evt.eventType, EVENT_TYPE_DELIVERY_FAILURE)) { 1156 if(!sendEventDeliveryFailed(eventFilter)) { 1157 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1158 return; 1159 } 1160 } else if(Objects.equals(evt.eventType, EVENT_TYPE_READ_STATUS)) { 1161 if(!sendEventReadStatusChanged(eventFilter)) { 1162 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1163 return; 1164 } 1165 } else if(Objects.equals(evt.eventType, EVENT_TYPE_CONVERSATION)) { 1166 if(!sendEventConversationChanged(eventFilter)) { 1167 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1168 return; 1169 } 1170 } else if(Objects.equals(evt.eventType, EVENT_TYPE_PRESENCE)) { 1171 if(!sendEventParticipantPresenceChanged(eventFilter)) { 1172 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1173 return; 1174 } 1175 } else if(Objects.equals(evt.eventType, EVENT_TYPE_CHAT_STATE)) { 1176 if(!sendEventParticipantChatstateChanged(eventFilter)) { 1177 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1178 return; 1179 } 1180 } 1181 1182 try { 1183 mMnsClient.sendEvent(evt.encode(), mMasId); 1184 } catch (UnsupportedEncodingException ex) { 1185 /* do nothing */ 1186 if (D) Log.e(TAG, "Exception - should not happen: ",ex); 1187 } 1188 } 1189 1190 private void initMsgList() throws RemoteException { 1191 if (V) Log.d(TAG, "initMsgList"); 1192 UserManager manager = UserManager.get(mContext); 1193 if (manager == null || !manager.isUserUnlocked()) return; 1194 1195 if (mEnableSmsMms) { 1196 HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>(); 1197 1198 Cursor c; 1199 try { 1200 c = mResolver.query(Sms.CONTENT_URI, 1201 SMS_PROJECTION_SHORT, null, null, null); 1202 } catch (SQLiteException e) { 1203 Log.e(TAG, "Failed to initialize the list of messages: " + e.toString()); 1204 return; 1205 } 1206 1207 try { 1208 if (c != null && c.moveToFirst()) { 1209 do { 1210 long id = c.getLong(c.getColumnIndex(Sms._ID)); 1211 int type = c.getInt(c.getColumnIndex(Sms.TYPE)); 1212 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 1213 int read = c.getInt(c.getColumnIndex(Sms.READ)); 1214 1215 Msg msg = new Msg(id, type, threadId, read); 1216 msgListSms.put(id, msg); 1217 } while (c.moveToNext()); 1218 } 1219 } finally { 1220 if (c != null) c.close(); 1221 } 1222 1223 synchronized(getMsgListSms()) { 1224 getMsgListSms().clear(); 1225 setMsgListSms(msgListSms, true); // Set initial folder version counter 1226 } 1227 1228 HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>(); 1229 1230 c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null); 1231 try { 1232 if (c != null && c.moveToFirst()) { 1233 do { 1234 long id = c.getLong(c.getColumnIndex(Mms._ID)); 1235 int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 1236 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 1237 int read = c.getInt(c.getColumnIndex(Mms.READ)); 1238 1239 Msg msg = new Msg(id, type, threadId, read); 1240 msgListMms.put(id, msg); 1241 } while (c.moveToNext()); 1242 } 1243 } finally { 1244 if (c != null) c.close(); 1245 } 1246 1247 synchronized(getMsgListMms()) { 1248 getMsgListMms().clear(); 1249 setMsgListMms(msgListMms, true); // Set initial folder version counter 1250 } 1251 } 1252 1253 if(mAccount != null) { 1254 HashMap<Long, Msg> msgList = new HashMap<Long, Msg>(); 1255 Uri uri = mMessageUri; 1256 Cursor c = mProviderClient.query(uri, MSG_PROJECTION_SHORT, null, null, null); 1257 1258 try { 1259 if (c != null && c.moveToFirst()) { 1260 do { 1261 long id = c.getLong(c.getColumnIndex(MessageColumns._ID)); 1262 long folderId = c.getInt(c.getColumnIndex( 1263 BluetoothMapContract.MessageColumns.FOLDER_ID)); 1264 int readFlag = c.getInt(c.getColumnIndex( 1265 BluetoothMapContract.MessageColumns.FLAG_READ)); 1266 Msg msg = new Msg(id, folderId, readFlag); 1267 msgList.put(id, msg); 1268 } while (c.moveToNext()); 1269 } 1270 } finally { 1271 if (c != null) c.close(); 1272 } 1273 1274 synchronized(getMsgListMsg()) { 1275 getMsgListMsg().clear(); 1276 setMsgListMsg(msgList, true); 1277 } 1278 } 1279 } 1280 1281 private void initContactsList() throws RemoteException { 1282 if (V) Log.d(TAG, "initContactsList"); 1283 if(mContactUri == null) { 1284 if (D) Log.d(TAG, "initContactsList() no mContactUri - nothing to init"); 1285 return; 1286 } 1287 Uri uri = mContactUri; 1288 Cursor c = mProviderClient.query(uri, 1289 BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION, 1290 null, null, null); 1291 Map<String, BluetoothMapConvoContactElement> contactList = 1292 new HashMap<String, BluetoothMapConvoContactElement>(); 1293 try { 1294 if (c != null && c.moveToFirst()) { 1295 ConvoContactInfo cInfo = new ConvoContactInfo(); 1296 cInfo.setConvoColunms(c); 1297 do { 1298 long convoId = c.getLong(cInfo.mContactColConvoId); 1299 if (convoId == 0) 1300 continue; 1301 if (V) BluetoothMapUtils.printCursor(c); 1302 String uci = c.getString(cInfo.mContactColUci); 1303 String name = c.getString(cInfo.mContactColName); 1304 String displayName = c.getString(cInfo.mContactColNickname); 1305 String presenceStatus = c.getString(cInfo.mContactColPresenceText); 1306 int presenceState = c.getInt(cInfo.mContactColPresenceState); 1307 long lastActivity = c.getLong(cInfo.mContactColLastActive); 1308 int chatState = c.getInt(cInfo.mContactColChatState); 1309 int priority = c.getInt(cInfo.mContactColPriority); 1310 String btUid = c.getString(cInfo.mContactColBtUid); 1311 BluetoothMapConvoContactElement contact = 1312 new BluetoothMapConvoContactElement(uci, name, displayName, 1313 presenceStatus, presenceState, lastActivity, chatState, 1314 priority, btUid); 1315 contactList.put(uci, contact); 1316 } while (c.moveToNext()); 1317 } 1318 } finally { 1319 if (c != null) c.close(); 1320 } 1321 synchronized(getContactList()) { 1322 getContactList().clear(); 1323 setContactList(contactList, true); 1324 } 1325 } 1326 1327 private void handleMsgListChangesSms() { 1328 if (V) Log.d(TAG, "handleMsgListChangesSms"); 1329 1330 HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>(); 1331 boolean listChanged = false; 1332 1333 Cursor c; 1334 synchronized(getMsgListSms()) { 1335 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1336 c = mResolver.query(Sms.CONTENT_URI, 1337 SMS_PROJECTION_SHORT, null, null, null); 1338 } else { 1339 c = mResolver.query(Sms.CONTENT_URI, 1340 SMS_PROJECTION_SHORT_EXT, null, null, null); 1341 } 1342 try { 1343 if (c != null && c.moveToFirst()) { 1344 do { 1345 long id = c.getLong(c.getColumnIndex(Sms._ID)); 1346 int type = c.getInt(c.getColumnIndex(Sms.TYPE)); 1347 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 1348 int read = c.getInt(c.getColumnIndex(Sms.READ)); 1349 1350 Msg msg = getMsgListSms().remove(id); 1351 1352 /* We must filter out any actions made by the MCE, hence do not send e.g. 1353 * a message deleted and/or MessageShift for messages deleted by the MCE. */ 1354 1355 if (msg == null) { 1356 /* New message */ 1357 msg = new Msg(id, type, threadId, read); 1358 msgListSms.put(id, msg); 1359 listChanged = true; 1360 Event evt; 1361 if (mTransmitEvents && // extract contact details only if needed 1362 mMapEventReportVersion > 1363 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1364 String date = BluetoothMapUtils.getDateTimeString( 1365 c.getLong(c.getColumnIndex(Sms.DATE))); 1366 String subject = c.getString(c.getColumnIndex(Sms.BODY)); 1367 String name = ""; 1368 String phone = ""; 1369 if (type == 1) { //inbox 1370 phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); 1371 if (phone != null && !phone.isEmpty()) { 1372 name = BluetoothMapContent.getContactNameFromPhone(phone, 1373 mResolver); 1374 if(name == null || name.isEmpty()){ 1375 name = phone; 1376 } 1377 }else{ 1378 name = phone; 1379 } 1380 } else { 1381 TelephonyManager tm = 1382 (TelephonyManager)mContext.getSystemService( 1383 Context.TELEPHONY_SERVICE); 1384 if (tm != null) { 1385 phone = tm.getLine1Number(); 1386 name = tm.getLine1AlphaTag(); 1387 if(name == null || name.isEmpty()){ 1388 name = phone; 1389 } 1390 } 1391 } 1392 String priority = "no";// no priority for sms 1393 /* Incoming message from the network */ 1394 if (mMapEventReportVersion == 1395 BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1396 evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type), 1397 mSmsType, date, subject, name, priority); 1398 } else { 1399 evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type), 1400 mSmsType, date, subject, name, priority, 1401 (long)threadId, null); 1402 } 1403 } else { 1404 /* Incoming message from the network */ 1405 evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type), 1406 null, mSmsType); 1407 } 1408 sendEvent(evt); 1409 } else { 1410 /* Existing message */ 1411 if (type != msg.type) { 1412 listChanged = true; 1413 Log.d(TAG, "new type: " + type + " old type: " + msg.type); 1414 String oldFolder = getSmsFolderName(msg.type); 1415 String newFolder = getSmsFolderName(type); 1416 // Filter out the intermediate outbox steps 1417 if(!oldFolder.equalsIgnoreCase(newFolder)) { 1418 Event evt = new Event(EVENT_TYPE_SHIFT, id, 1419 getSmsFolderName(type), oldFolder, mSmsType); 1420 sendEvent(evt); 1421 } 1422 msg.type = type; 1423 } else if(threadId != msg.threadId) { 1424 listChanged = true; 1425 Log.d(TAG, "Message delete change: type: " + type 1426 + " old type: " + msg.type 1427 + "\n threadId: " + threadId 1428 + " old threadId: " + msg.threadId); 1429 if(threadId == DELETED_THREAD_ID) { // Message deleted 1430 // TODO: 1431 // We shall only use the folder attribute, but can't remember 1432 // wether to set it to "deleted" or the name of the folder 1433 // from which the message have been deleted. 1434 // "old_folder" used only for MessageShift event 1435 Event evt = new Event(EVENT_TYPE_DELETE, id, 1436 getSmsFolderName(msg.type), null, mSmsType); 1437 sendEvent(evt); 1438 msg.threadId = threadId; 1439 } else { // Undelete 1440 Event evt = new Event(EVENT_TYPE_SHIFT, id, 1441 getSmsFolderName(msg.type), 1442 BluetoothMapContract.FOLDER_NAME_DELETED, mSmsType); 1443 sendEvent(evt); 1444 msg.threadId = threadId; 1445 } 1446 } 1447 if(read != msg.flagRead) { 1448 listChanged = true; 1449 msg.flagRead = read; 1450 if (mMapEventReportVersion > 1451 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1452 Event evt = new Event(EVENT_TYPE_READ_STATUS, id, 1453 getSmsFolderName(msg.type), mSmsType); 1454 sendEvent(evt); 1455 } 1456 } 1457 msgListSms.put(id, msg); 1458 } 1459 } while (c.moveToNext()); 1460 } 1461 } finally { 1462 if (c != null) c.close(); 1463 } 1464 1465 for (Msg msg : getMsgListSms().values()) { 1466 // "old_folder" used only for MessageShift event 1467 Event evt = new Event(EVENT_TYPE_DELETE, msg.id, 1468 getSmsFolderName(msg.type), null, mSmsType); 1469 sendEvent(evt); 1470 listChanged = true; 1471 } 1472 1473 setMsgListSms(msgListSms, listChanged); 1474 } 1475 } 1476 1477 private void handleMsgListChangesMms() { 1478 if (V) Log.d(TAG, "handleMsgListChangesMms"); 1479 1480 HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>(); 1481 boolean listChanged = false; 1482 Cursor c; 1483 synchronized(getMsgListMms()) { 1484 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1485 c = mResolver.query(Mms.CONTENT_URI, 1486 MMS_PROJECTION_SHORT, null, null, null); 1487 } else { 1488 c = mResolver.query(Mms.CONTENT_URI, 1489 MMS_PROJECTION_SHORT_EXT, null, null, null); 1490 } 1491 1492 try{ 1493 if (c != null && c.moveToFirst()) { 1494 do { 1495 long id = c.getLong(c.getColumnIndex(Mms._ID)); 1496 int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 1497 int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE)); 1498 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 1499 // TODO: Go through code to see if we have an issue with mismatch in types 1500 // for threadId. Seems to be a long in DB?? 1501 int read = c.getInt(c.getColumnIndex(Mms.READ)); 1502 1503 Msg msg = getMsgListMms().remove(id); 1504 1505 /* We must filter out any actions made by the MCE, hence do not send 1506 * e.g. a message deleted and/or MessageShift for messages deleted by the 1507 * MCE.*/ 1508 1509 if (msg == null) { 1510 /* New message - only notify on retrieve conf */ 1511 listChanged = true; 1512 if (getMmsFolderName(type).equalsIgnoreCase( 1513 BluetoothMapContract.FOLDER_NAME_INBOX) && 1514 mtype != MESSAGE_TYPE_RETRIEVE_CONF) { 1515 continue; 1516 } 1517 msg = new Msg(id, type, threadId, read); 1518 msgListMms.put(id, msg); 1519 Event evt; 1520 if (mTransmitEvents && // extract contact details only if needed 1521 mMapEventReportVersion != 1522 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1523 String date = BluetoothMapUtils.getDateTimeString( 1524 c.getLong(c.getColumnIndex(Mms.DATE))); 1525 String subject = c.getString(c.getColumnIndex(Mms.SUBJECT)); 1526 if (subject == null || subject.length() == 0) { 1527 /* Get subject from mms text body parts - if any exists */ 1528 subject = BluetoothMapContent.getTextPartsMms(mResolver, id); 1529 } 1530 int tmpPri = c.getInt(c.getColumnIndex(Mms.PRIORITY)); 1531 Log.d(TAG, "TEMP handleMsgListChangesMms, " + 1532 "newMessage 'read' state: " + read + 1533 "priority: " + tmpPri); 1534 1535 String address = BluetoothMapContent.getAddressMms( 1536 mResolver,id,BluetoothMapContent.MMS_FROM); 1537 String priority = "no"; 1538 if(tmpPri == PduHeaders.PRIORITY_HIGH) 1539 priority = "yes"; 1540 1541 /* Incoming message from the network */ 1542 if (mMapEventReportVersion == 1543 BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1544 evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), 1545 TYPE.MMS, date, subject, address, priority); 1546 } else { 1547 evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), 1548 TYPE.MMS, date, subject, address, priority, 1549 (long)threadId, null); 1550 } 1551 1552 } else { 1553 /* Incoming message from the network */ 1554 evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), 1555 null, TYPE.MMS); 1556 } 1557 1558 sendEvent(evt); 1559 } else { 1560 /* Existing message */ 1561 if (type != msg.type) { 1562 Log.d(TAG, "new type: " + type + " old type: " + msg.type); 1563 Event evt; 1564 listChanged = true; 1565 if(!msg.localInitiatedSend) { 1566 // Only send events about local initiated changes 1567 evt = new Event(EVENT_TYPE_SHIFT, id, getMmsFolderName(type), 1568 getMmsFolderName(msg.type), TYPE.MMS); 1569 sendEvent(evt); 1570 } 1571 msg.type = type; 1572 1573 if (getMmsFolderName(type).equalsIgnoreCase( 1574 BluetoothMapContract.FOLDER_NAME_SENT) 1575 && msg.localInitiatedSend) { 1576 // Stop tracking changes for this message 1577 msg.localInitiatedSend = false; 1578 evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id, 1579 getMmsFolderName(type), null, TYPE.MMS); 1580 sendEvent(evt); 1581 } 1582 } else if(threadId != msg.threadId) { 1583 Log.d(TAG, "Message delete change: type: " + type + " old type: " 1584 + msg.type 1585 + "\n threadId: " + threadId + " old threadId: " 1586 + msg.threadId); 1587 listChanged = true; 1588 if(threadId == DELETED_THREAD_ID) { // Message deleted 1589 // "old_folder" used only for MessageShift event 1590 Event evt = new Event(EVENT_TYPE_DELETE, id, 1591 getMmsFolderName(msg.type), null, TYPE.MMS); 1592 sendEvent(evt); 1593 msg.threadId = threadId; 1594 } else { // Undelete 1595 Event evt = new Event(EVENT_TYPE_SHIFT, id, 1596 getMmsFolderName(msg.type), 1597 BluetoothMapContract.FOLDER_NAME_DELETED, TYPE.MMS); 1598 sendEvent(evt); 1599 msg.threadId = threadId; 1600 } 1601 } 1602 if(read != msg.flagRead) { 1603 listChanged = true; 1604 msg.flagRead = read; 1605 if (mMapEventReportVersion > 1606 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1607 Event evt = new Event(EVENT_TYPE_READ_STATUS, id, 1608 getMmsFolderName(msg.type), TYPE.MMS); 1609 sendEvent(evt); 1610 } 1611 } 1612 msgListMms.put(id, msg); 1613 } 1614 } while (c.moveToNext()); 1615 1616 } 1617 } finally { 1618 if (c != null) c.close(); 1619 } 1620 for (Msg msg : getMsgListMms().values()) { 1621 // "old_folder" used only for MessageShift event 1622 Event evt = new Event(EVENT_TYPE_DELETE, msg.id, 1623 getMmsFolderName(msg.type), null, TYPE.MMS); 1624 sendEvent(evt); 1625 listChanged = true; 1626 } 1627 setMsgListMms(msgListMms, listChanged); 1628 } 1629 } 1630 1631 private void handleMsgListChangesMsg(Uri uri) throws RemoteException{ 1632 if (V) Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString()); 1633 1634 // TODO: Change observer to handle accountId and message ID if present 1635 1636 HashMap<Long, Msg> msgList = new HashMap<Long, Msg>(); 1637 Cursor c; 1638 boolean listChanged = false; 1639 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1640 c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT, null, null, null); 1641 } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1642 c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT, null, null, null); 1643 } else { 1644 c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT2, null, null, null); 1645 } 1646 synchronized(getMsgListMsg()) { 1647 try { 1648 if (c != null && c.moveToFirst()) { 1649 do { 1650 long id = c.getLong(c.getColumnIndex( 1651 BluetoothMapContract.MessageColumns._ID)); 1652 int folderId = c.getInt(c.getColumnIndex( 1653 BluetoothMapContract.MessageColumns.FOLDER_ID)); 1654 int readFlag = c.getInt(c.getColumnIndex( 1655 BluetoothMapContract.MessageColumns.FLAG_READ)); 1656 Msg msg = getMsgListMsg().remove(id); 1657 BluetoothMapFolderElement folderElement = mFolders.getFolderById(folderId); 1658 String newFolder; 1659 if(folderElement != null) { 1660 newFolder = folderElement.getFullPath(); 1661 } else { 1662 // This can happen if a new folder is created while connected 1663 newFolder = "unknown"; 1664 } 1665 /* We must filter out any actions made by the MCE, hence do not send e.g. 1666 * a message deleted and/or MessageShift for messages deleted by the MCE. */ 1667 if (msg == null) { 1668 listChanged = true; 1669 /* New message - created with message unread */ 1670 msg = new Msg(id, folderId, 0, readFlag); 1671 msgList.put(id, msg); 1672 Event evt; 1673 /* Incoming message from the network */ 1674 if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1675 String date = BluetoothMapUtils.getDateTimeString( 1676 c.getLong(c.getColumnIndex( 1677 BluetoothMapContract.MessageColumns.DATE))); 1678 String subject = c.getString(c.getColumnIndex( 1679 BluetoothMapContract.MessageColumns.SUBJECT)); 1680 String address = c.getString(c.getColumnIndex( 1681 BluetoothMapContract.MessageColumns.FROM_LIST)); 1682 String priority = "no"; 1683 if(c.getInt(c.getColumnIndex( 1684 BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY)) 1685 == 1) 1686 priority = "yes"; 1687 if (mMapEventReportVersion == 1688 BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1689 evt = new Event(EVENT_TYPE_NEW, id, newFolder, 1690 mAccount.getType(), date, subject, address, priority); 1691 } else { 1692 long thread_id = c.getLong(c.getColumnIndex( 1693 BluetoothMapContract.MessageColumns.THREAD_ID)); 1694 String thread_name = c.getString(c.getColumnIndex( 1695 BluetoothMapContract.MessageColumns.THREAD_NAME)); 1696 evt = new Event(EVENT_TYPE_NEW, id, newFolder, 1697 mAccount.getType(), date, subject, address, priority, 1698 thread_id, thread_name); 1699 } 1700 } else { 1701 evt = new Event(EVENT_TYPE_NEW, id, newFolder, null, TYPE.EMAIL); 1702 } 1703 sendEvent(evt); 1704 } else { 1705 /* Existing message */ 1706 if (folderId != msg.folderId && msg.folderId != -1) { 1707 if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: " 1708 + msg.folderId); 1709 BluetoothMapFolderElement oldFolderElement = 1710 mFolders.getFolderById(msg.folderId); 1711 String oldFolder; 1712 listChanged = true; 1713 if(oldFolderElement != null) { 1714 oldFolder = oldFolderElement.getFullPath(); 1715 } else { 1716 // This can happen if a new folder is created while connected 1717 oldFolder = "unknown"; 1718 } 1719 BluetoothMapFolderElement deletedFolder = 1720 mFolders.getFolderByName( 1721 BluetoothMapContract.FOLDER_NAME_DELETED); 1722 BluetoothMapFolderElement sentFolder = 1723 mFolders.getFolderByName( 1724 BluetoothMapContract.FOLDER_NAME_SENT); 1725 /* 1726 * If the folder is now 'deleted', send a deleted-event in stead of 1727 * a shift or if message is sent initiated by MAP Client, then send 1728 * sending-success otherwise send folderShift 1729 */ 1730 if(deletedFolder != null && deletedFolder.getFolderId() 1731 == folderId) { 1732 // "old_folder" used only for MessageShift event 1733 Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder, 1734 null, mAccount.getType()); 1735 sendEvent(evt); 1736 } else if(sentFolder != null 1737 && sentFolder.getFolderId() == folderId 1738 && msg.localInitiatedSend) { 1739 if(msg.transparent) { 1740 mResolver.delete( 1741 ContentUris.withAppendedId(mMessageUri, id), 1742 null, null); 1743 } else { 1744 msg.localInitiatedSend = false; 1745 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, 1746 oldFolder, null, mAccount.getType()); 1747 sendEvent(evt); 1748 } 1749 } else { 1750 if (!oldFolder.equalsIgnoreCase("root")) { 1751 Event evt = new Event(EVENT_TYPE_SHIFT, id, newFolder, 1752 oldFolder, mAccount.getType()); 1753 sendEvent(evt); 1754 } 1755 } 1756 msg.folderId = folderId; 1757 } 1758 if(readFlag != msg.flagRead) { 1759 listChanged = true; 1760 1761 if (mMapEventReportVersion > 1762 BluetoothMapUtils.MAP_EVENT_REPORT_V10) { 1763 Event evt = new Event(EVENT_TYPE_READ_STATUS, id, newFolder, 1764 mAccount.getType()); 1765 sendEvent(evt); 1766 msg.flagRead = readFlag; 1767 } 1768 } 1769 1770 msgList.put(id, msg); 1771 } 1772 } while (c.moveToNext()); 1773 } 1774 } finally { 1775 if (c != null) c.close(); 1776 } 1777 // For all messages no longer in the database send a delete notification 1778 for (Msg msg : getMsgListMsg().values()) { 1779 BluetoothMapFolderElement oldFolderElement = mFolders.getFolderById(msg.folderId); 1780 String oldFolder; 1781 listChanged = true; 1782 if(oldFolderElement != null) { 1783 oldFolder = oldFolderElement.getFullPath(); 1784 } else { 1785 oldFolder = "unknown"; 1786 } 1787 /* Some e-mail clients delete the message after sending, and creates a 1788 * new message in sent. We cannot track the message anymore, hence send both a 1789 * send success and delete message. 1790 */ 1791 if(msg.localInitiatedSend) { 1792 msg.localInitiatedSend = false; 1793 // If message is send with transparency don't set folder as message is deleted 1794 if (msg.transparent) 1795 oldFolder = null; 1796 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null, 1797 mAccount.getType()); 1798 sendEvent(evt); 1799 } 1800 /* As this message deleted is only send on a real delete - don't set folder. 1801 * - only send delete event if message is not sent with transparency 1802 */ 1803 if (!msg.transparent) { 1804 1805 // "old_folder" used only for MessageShift event 1806 Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder, 1807 null, mAccount.getType()); 1808 sendEvent(evt); 1809 } 1810 } 1811 setMsgListMsg(msgList, listChanged); 1812 } 1813 } 1814 1815 private void handleMsgListChanges(Uri uri) { 1816 if(uri.getAuthority().equals(mAuthority)) { 1817 try { 1818 if(D) Log.d(TAG, "handleMsgListChanges: account type = " 1819 + mAccount.getType().toString()); 1820 handleMsgListChangesMsg(uri); 1821 } catch(RemoteException e) { 1822 mMasInstance.restartObexServerSession(); 1823 Log.w(TAG, "Problems contacting the ContentProvider in mas Instance " 1824 + mMasId + " restaring ObexServerSession"); 1825 } 1826 1827 } 1828 // TODO: check to see if there could be problem with IM and SMS in one instance 1829 if (mEnableSmsMms) { 1830 handleMsgListChangesSms(); 1831 handleMsgListChangesMms(); 1832 } 1833 } 1834 1835 private void handleContactListChanges(Uri uri) { 1836 if (uri.getAuthority().equals(mAuthority)) { 1837 try { 1838 if (V) Log.v(TAG,"handleContactListChanges uri: " + uri.toString()); 1839 Cursor c = null; 1840 boolean listChanged = false; 1841 try { 1842 ConvoContactInfo cInfo = new ConvoContactInfo(); 1843 1844 if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10 1845 && mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1846 c = mProviderClient 1847 .query(mContactUri, 1848 BluetoothMapContract. 1849 BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION, 1850 null, null, null); 1851 cInfo.setConvoColunms(c); 1852 } else { 1853 if (V) Log.v(TAG,"handleContactListChanges MAP version does not" + 1854 "support convocontact notifications"); 1855 return; 1856 } 1857 1858 HashMap<String, BluetoothMapConvoContactElement> contactList = 1859 new HashMap<String, 1860 BluetoothMapConvoContactElement>(getContactList().size()); 1861 1862 synchronized (getContactList()) { 1863 if (c != null && c.moveToFirst()) { 1864 do { 1865 String uci = c.getString(cInfo.mContactColUci); 1866 long convoId = c.getLong(cInfo.mContactColConvoId); 1867 if (convoId == 0) 1868 continue; 1869 1870 if (V) BluetoothMapUtils.printCursor(c); 1871 1872 BluetoothMapConvoContactElement contact = 1873 getContactList().remove(uci); 1874 1875 /* 1876 * We must filter out any actions made by the 1877 * MCE, hence do not send e.g. a message deleted 1878 * and/or MessageShift for messages deleted by 1879 * the MCE. 1880 */ 1881 if (contact == null) { 1882 listChanged = true; 1883 /* 1884 * New contact - added to conversation and 1885 * tracked here 1886 */ 1887 if (mMapEventReportVersion 1888 != BluetoothMapUtils.MAP_EVENT_REPORT_V10 1889 && mMapEventReportVersion 1890 != BluetoothMapUtils.MAP_EVENT_REPORT_V11) { 1891 Event evt; 1892 String name = c 1893 .getString(cInfo.mContactColName); 1894 String displayName = c 1895 .getString(cInfo.mContactColNickname); 1896 String presenceStatus = c 1897 .getString(cInfo.mContactColPresenceText); 1898 int presenceState = c 1899 .getInt(cInfo.mContactColPresenceState); 1900 long lastActivity = c 1901 .getLong(cInfo.mContactColLastActive); 1902 int chatState = c 1903 .getInt(cInfo.mContactColChatState); 1904 int priority = c 1905 .getInt(cInfo.mContactColPriority); 1906 String btUid = c 1907 .getString(cInfo.mContactColBtUid); 1908 1909 // Get Conversation information for 1910 // event 1911// Uri convoUri = Uri 1912// .parse(mAccount.mBase_uri 1913// + "/" 1914// + BluetoothMapContract.TABLE_CONVERSATION); 1915// String whereClause = "contacts._id = " 1916// + convoId; 1917// Cursor cConvo = mProviderClient 1918// .query(convoUri, 1919// BluetoothMapContract.BT_CONVERSATION_PROJECTION, 1920// whereClause, null, null); 1921 // TODO: will move out of the loop when merged with CB's 1922 // changes make sure to look up col index out side loop 1923 String convoName = null; 1924// if (cConvo != null 1925// && cConvo.moveToFirst()) { 1926// convoName = cConvo 1927// .getString(cConvo 1928// .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME)); 1929// } 1930 1931 contact = new BluetoothMapConvoContactElement( 1932 uci, name, displayName, 1933 presenceStatus, presenceState, 1934 lastActivity, chatState, 1935 priority, btUid); 1936 1937 contactList.put(uci, contact); 1938 1939 evt = new Event( 1940 EVENT_TYPE_CONVERSATION, 1941 uci, 1942 mAccount.getType(), 1943 name, 1944 String.valueOf(priority), 1945 BluetoothMapUtils 1946 .getDateTimeString(lastActivity), 1947 convoId, convoName, 1948 presenceState, presenceStatus, 1949 chatState); 1950 1951 sendEvent(evt); 1952 } 1953 1954 } else { 1955 // Not new - compare updated content 1956// Uri convoUri = Uri 1957// .parse(mAccount.mBase_uri 1958// + "/" 1959// + BluetoothMapContract.TABLE_CONVERSATION); 1960 // TODO: Should be changed to own provider interface name 1961// String whereClause = "contacts._id = " 1962// + convoId; 1963// Cursor cConvo = mProviderClient 1964// .query(convoUri, 1965// BluetoothMapContract.BT_CONVERSATION_PROJECTION, 1966// whereClause, null, null); 1967// // TODO: will move out of the loop when merged with CB's 1968// // changes make sure to look up col index out side loop 1969 String convoName = null; 1970// if (cConvo != null && cConvo.moveToFirst()) { 1971// convoName = cConvo 1972// .getString(cConvo 1973// .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME)); 1974// } 1975 1976 // Check if presence is updated 1977 int presenceState = c.getInt(cInfo.mContactColPresenceState); 1978 String presenceStatus = c.getString( 1979 cInfo.mContactColPresenceText); 1980 String currentPresenceStatus = contact 1981 .getPresenceStatus(); 1982 if (contact.getPresenceAvailability() != presenceState 1983 || !Objects.equals(currentPresenceStatus, 1984 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 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) { 2142 Log.w(TAG, "Set delete status " + status + " failed."); 2143 } 2144 return res; 2145 } 2146 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 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 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 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 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 */ 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 */ 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 2430 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 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; 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())) { 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 2648 public long sendMmsMessage(String folder, String toAddress, 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, toAddress, 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 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 */ 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 } 2726 2727 private long pushMmsToFolder(int folder, String toAddress, BluetoothMapbMessageMime msg) { 2728 /** 2729 * strategy: 2730 * 1) parse msg into parts + header 2731 * 2) create thread id (abuse the ease of adding an SMS to get id for thread) 2732 * 3) push parts into content://mms/parts/ table 2733 * 3) 2734 */ 2735 2736 ContentValues values = new ContentValues(); 2737 values.put(Mms.MESSAGE_BOX, folder); 2738 values.put(Mms.READ, 0); 2739 values.put(Mms.SEEN, 0); 2740 if(msg.getSubject() != null) { 2741 values.put(Mms.SUBJECT, msg.getSubject()); 2742 } else { 2743 values.put(Mms.SUBJECT, ""); 2744 } 2745 2746 if(msg.getSubject() != null && msg.getSubject().length() > 0) { 2747 values.put(Mms.SUBJECT_CHARSET, 106); 2748 } 2749 values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related"); 2750 values.put(Mms.EXPIRY, 604800); 2751 values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR); 2752 values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ); 2753 values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION); 2754 values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL); 2755 values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO); 2756 values.put(Mms.TRANSACTION_ID, "T"+ Long.toHexString(System.currentTimeMillis())); 2757 values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO); 2758 values.put(Mms.LOCKED, 0); 2759 if(msg.getTextOnly()) 2760 values.put(Mms.TEXT_ONLY, true); 2761 values.put(Mms.MESSAGE_SIZE, msg.getSize()); 2762 2763 // Get thread id 2764 Set<String> recipients = new HashSet<String>(); 2765 recipients.addAll(Arrays.asList(toAddress)); 2766 values.put(Mms.THREAD_ID, Telephony.Threads.getOrCreateThreadId(mContext, recipients)); 2767 Uri uri = Mms.CONTENT_URI; 2768 2769 synchronized (getMsgListMms()) { 2770 2771 uri = mResolver.insert(uri, values); 2772 2773 if (uri == null) { 2774 // unable to insert MMS 2775 Log.e(TAG, "Unabled to insert MMS " + values + "Uri: " + uri); 2776 return -1; 2777 } 2778 /* As we already have all the values we need, we could skip the query, but 2779 doing the query ensures we get any changes made by the content provider 2780 at insert. */ 2781 Cursor c = mResolver.query(uri, MMS_PROJECTION_SHORT, null, null, null); 2782 try { 2783 if (c != null && c.moveToFirst()) { 2784 long id = c.getLong(c.getColumnIndex(Mms._ID)); 2785 int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 2786 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 2787 int readStatus = c.getInt(c.getColumnIndex(Mms.READ)); 2788 2789 /* We must filter out any actions made by the MCE. Add the new message to 2790 * the list of known messages. */ 2791 2792 Msg newMsg = new Msg(id, type, threadId, readStatus); 2793 newMsg.localInitiatedSend = true; 2794 getMsgListMms().put(id, newMsg); 2795 c.close(); 2796 } 2797 } finally { 2798 if (c != null) c.close(); 2799 } 2800 } // Done adding changes, unlock access to mMsgListMms to allow sending MMS events again 2801 2802 long handle = Long.parseLong(uri.getLastPathSegment()); 2803 if (V) Log.v(TAG, " NEW URI " + uri.toString()); 2804 2805 try { 2806 if(msg.getMimeParts() == null) { 2807 /* Perhaps this message have been deleted, and no longer have any content, 2808 * but only headers */ 2809 Log.w(TAG, "No MMS parts present..."); 2810 } else { 2811 if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size() 2812 + " parts to the data base."); 2813 for(MimePart part : msg.getMimeParts()) { 2814 int count = 0; 2815 count++; 2816 values.clear(); 2817 if(part.mContentType != null && 2818 part.mContentType.toUpperCase().contains("TEXT")) { 2819 values.put(Mms.Part.CONTENT_TYPE, "text/plain"); 2820 values.put(Mms.Part.CHARSET, 106); 2821 if(part.mPartName != null) { 2822 values.put(Mms.Part.FILENAME, part.mPartName); 2823 values.put(Mms.Part.NAME, part.mPartName); 2824 } else { 2825 values.put(Mms.Part.FILENAME, "text_" + count +".txt"); 2826 values.put(Mms.Part.NAME, "text_" + count +".txt"); 2827 } 2828 // Ensure we have "ci" set 2829 if(part.mContentId != null) { 2830 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2831 } else { 2832 if(part.mPartName != null) { 2833 values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">"); 2834 } else { 2835 values.put(Mms.Part.CONTENT_ID, "<text_" + count + ">"); 2836 } 2837 } 2838 // Ensure we have "cl" set 2839 if(part.mContentLocation != null) { 2840 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2841 } else { 2842 if(part.mPartName != null) { 2843 values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".txt"); 2844 } else { 2845 values.put(Mms.Part.CONTENT_LOCATION, "text_" + count + ".txt"); 2846 } 2847 } 2848 2849 if(part.mContentDisposition != null) { 2850 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2851 } 2852 values.put(Mms.Part.TEXT, part.getDataAsString()); 2853 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part"); 2854 uri = mResolver.insert(uri, values); 2855 if(V) Log.v(TAG, "Added TEXT part"); 2856 2857 } else if (part.mContentType != null && 2858 part.mContentType.toUpperCase().contains("SMIL")){ 2859 values.put(Mms.Part.SEQ, -1); 2860 values.put(Mms.Part.CONTENT_TYPE, "application/smil"); 2861 if(part.mContentId != null) { 2862 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2863 } else { 2864 values.put(Mms.Part.CONTENT_ID, "<smil_" + count + ">"); 2865 } 2866 if(part.mContentLocation != null) { 2867 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2868 } else { 2869 values.put(Mms.Part.CONTENT_LOCATION, "smil_" + count + ".xml"); 2870 } 2871 2872 if(part.mContentDisposition != null) 2873 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2874 values.put(Mms.Part.FILENAME, "smil.xml"); 2875 values.put(Mms.Part.NAME, "smil.xml"); 2876 values.put(Mms.Part.TEXT, new String(part.mData, "UTF-8")); 2877 2878 uri = Uri.parse(Mms.CONTENT_URI+ "/" + handle + "/part"); 2879 uri = mResolver.insert(uri, values); 2880 if (V) Log.v(TAG, "Added SMIL part"); 2881 2882 }else /*VIDEO/AUDIO/IMAGE*/ { 2883 writeMmsDataPart(handle, part, count); 2884 if (V) Log.v(TAG, "Added OTHER part"); 2885 } 2886 if (uri != null){ 2887 if (V) Log.v(TAG, "Added part with content-type: " + part.mContentType 2888 + " to Uri: " + uri.toString()); 2889 } 2890 } 2891 } 2892 } catch (UnsupportedEncodingException e) { 2893 Log.w(TAG, e); 2894 } catch (IOException e) { 2895 Log.w(TAG, e); 2896 } 2897 2898 values.clear(); 2899 values.put(Mms.Addr.CONTACT_ID, "null"); 2900 values.put(Mms.Addr.ADDRESS, "insert-address-token"); 2901 values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_FROM); 2902 values.put(Mms.Addr.CHARSET, 106); 2903 2904 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr"); 2905 uri = mResolver.insert(uri, values); 2906 if (uri != null && V){ 2907 Log.v(TAG, " NEW URI " + uri.toString()); 2908 } 2909 2910 values.clear(); 2911 values.put(Mms.Addr.CONTACT_ID, "null"); 2912 values.put(Mms.Addr.ADDRESS, toAddress); 2913 values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_TO); 2914 values.put(Mms.Addr.CHARSET, 106); 2915 2916 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr"); 2917 uri = mResolver.insert(uri, values); 2918 if (uri != null && V){ 2919 Log.v(TAG, " NEW URI " + uri.toString()); 2920 } 2921 return handle; 2922 } 2923 2924 2925 private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException{ 2926 ContentValues values = new ContentValues(); 2927 values.put(Mms.Part.MSG_ID, handle); 2928 if(part.mContentType != null) { 2929 values.put(Mms.Part.CONTENT_TYPE, part.mContentType); 2930 } else { 2931 Log.w(TAG, "MMS has no CONTENT_TYPE for part " + count); 2932 } 2933 if(part.mContentId != null) { 2934 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2935 } else { 2936 if(part.mPartName != null) { 2937 values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">"); 2938 } else { 2939 values.put(Mms.Part.CONTENT_ID, "<part_" + count + ">"); 2940 } 2941 } 2942 2943 if(part.mContentLocation != null) { 2944 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2945 } else { 2946 if(part.mPartName != null) { 2947 values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".dat"); 2948 } else { 2949 values.put(Mms.Part.CONTENT_LOCATION, "part_" + count + ".dat"); 2950 } 2951 } 2952 if(part.mContentDisposition != null) 2953 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2954 if(part.mPartName != null) { 2955 values.put(Mms.Part.FILENAME, part.mPartName); 2956 values.put(Mms.Part.NAME, part.mPartName); 2957 } else { 2958 /* We must set at least one part identifier */ 2959 values.put(Mms.Part.FILENAME, "part_" + count + ".dat"); 2960 values.put(Mms.Part.NAME, "part_" + count + ".dat"); 2961 } 2962 Uri partUri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part"); 2963 Uri res = mResolver.insert(partUri, values); 2964 2965 // Add data to part 2966 OutputStream os = mResolver.openOutputStream(res); 2967 os.write(part.mData); 2968 os.close(); 2969 } 2970 2971 2972 public void sendMessage(PushMsgInfo msgInfo, String msgBody) { 2973 2974 SmsManager smsMng = SmsManager.getDefault(); 2975 ArrayList<String> parts = smsMng.divideMessage(msgBody); 2976 msgInfo.parts = parts.size(); 2977 // We add a time stamp to differentiate delivery reports from each other for resent messages 2978 msgInfo.timestamp = Calendar.getInstance().getTime().getTime(); 2979 msgInfo.partsDelivered = 0; 2980 msgInfo.partsSent = 0; 2981 2982 ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(msgInfo.parts); 2983 ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(msgInfo.parts); 2984 2985 /* We handle the SENT intent in the MAP service, as this object 2986 * is destroyed at disconnect, hence if a disconnect occur while sending 2987 * a message, there is no intent handler to move the message from outbox 2988 * to the correct folder. 2989 * The correct solution would be to create a service that will start based on 2990 * the intent, if BT is turned off. */ 2991 2992 if (parts != null && parts.size() > 0) { 2993 for (int i = 0; i < msgInfo.parts; i++) { 2994 Intent intentDelivery, intentSent; 2995 2996 intentDelivery = new Intent(ACTION_MESSAGE_DELIVERY, null); 2997 /* Add msgId and part number to ensure the intents are different, and we 2998 * thereby get an intent for each msg part. 2999 * setType is needed to create different intents for each message id/ time stamp, 3000 * as the extras are not used when comparing. */ 3001 intentDelivery.setType("message/" + Long.toString(msgInfo.id) + 3002 msgInfo.timestamp + i); 3003 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id); 3004 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, msgInfo.timestamp); 3005 PendingIntent pendingIntentDelivery = PendingIntent.getBroadcast(mContext, 0, 3006 intentDelivery, PendingIntent.FLAG_UPDATE_CURRENT); 3007 3008 intentSent = new Intent(ACTION_MESSAGE_SENT, null); 3009 /* Add msgId and part number to ensure the intents are different, and we 3010 * thereby get an intent for each msg part. 3011 * setType is needed to create different intents for each message id/ time stamp, 3012 * as the extras are not used when comparing. */ 3013 intentSent.setType("message/" + Long.toString(msgInfo.id) + msgInfo.timestamp + i); 3014 intentSent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id); 3015 intentSent.putExtra(EXTRA_MESSAGE_SENT_URI, msgInfo.uri.toString()); 3016 intentSent.putExtra(EXTRA_MESSAGE_SENT_RETRY, msgInfo.retry); 3017 intentSent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, msgInfo.transparent); 3018 3019 PendingIntent pendingIntentSent = PendingIntent.getBroadcast(mContext, 0, 3020 intentSent, PendingIntent.FLAG_UPDATE_CURRENT); 3021 3022 // We use the same pending intent for all parts, but do not set the one shot flag. 3023 deliveryIntents.add(pendingIntentDelivery); 3024 sentIntents.add(pendingIntentSent); 3025 } 3026 3027 Log.d(TAG, "sendMessage to " + msgInfo.phone); 3028 3029 smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents, 3030 deliveryIntents); 3031 } 3032 } 3033 3034 private class SmsBroadcastReceiver extends BroadcastReceiver { 3035 private final String[] ID_PROJECTION = new String[] { Sms._ID }; 3036 private final Uri UPDATE_STATUS_URI = Uri.withAppendedPath(Sms.CONTENT_URI, "/status"); 3037 3038 public void register() { 3039 Handler handler = new Handler(Looper.getMainLooper()); 3040 3041 IntentFilter intentFilter = new IntentFilter(); 3042 intentFilter.addAction(ACTION_MESSAGE_DELIVERY); 3043 try{ 3044 intentFilter.addDataType("message/*"); 3045 } catch (MalformedMimeTypeException e) { 3046 Log.e(TAG, "Wrong mime type!!!", e); 3047 } 3048 3049 mContext.registerReceiver(this, intentFilter, null, handler); 3050 } 3051 3052 public void unregister() { 3053 try { 3054 mContext.unregisterReceiver(this); 3055 } catch (IllegalArgumentException e) { 3056 /* do nothing */ 3057 } 3058 } 3059 3060 @Override 3061 public void onReceive(Context context, Intent intent) { 3062 String action = intent.getAction(); 3063 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3064 PushMsgInfo msgInfo = mPushMsgList.get(handle); 3065 3066 Log.d(TAG, "onReceive: action" + action); 3067 3068 if (msgInfo == null) { 3069 Log.d(TAG, "onReceive: no msgInfo found for handle " + handle); 3070 return; 3071 } 3072 3073 if (action.equals(ACTION_MESSAGE_SENT)) { 3074 int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, 3075 Activity.RESULT_CANCELED); 3076 msgInfo.partsSent++; 3077 if(result != Activity.RESULT_OK) { 3078 /* If just one of the parts in the message fails, we need to send the 3079 * entire message again 3080 */ 3081 msgInfo.failedSent = true; 3082 } 3083 if(D) Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent 3084 + ", msgInfo.parts = " + msgInfo.parts + " result = " + result); 3085 3086 if (msgInfo.partsSent == msgInfo.parts) { 3087 actionMessageSent(context, intent, msgInfo); 3088 } 3089 } else if (action.equals(ACTION_MESSAGE_DELIVERY)) { 3090 long timestamp = intent.getLongExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, 0); 3091 int status = -1; 3092 if(msgInfo.timestamp == timestamp) { 3093 msgInfo.partsDelivered++; 3094 } 3095 } else { 3096 Log.d(TAG, "onReceive: Unknown action " + action); 3097 } 3098 } 3099 3100 private void actionMessageSent(Context context, Intent intent, PushMsgInfo msgInfo) { 3101 /* As the MESSAGE_SENT intent is forwarded from the MAP service, we use the intent 3102 * to carry the result, as getResult() will not return the correct value. 3103 */ 3104 boolean delete = false; 3105 3106 if(D) Log.d(TAG,"actionMessageSent(): msgInfo.failedSent = " + msgInfo.failedSent); 3107 3108 msgInfo.sendInProgress = false; 3109 3110 if (!msgInfo.failedSent) { 3111 if(D) Log.d(TAG, "actionMessageSent: result OK"); 3112 if (msgInfo.transparent == 0) { 3113 if (!Sms.moveMessageToFolder(context, msgInfo.uri, 3114 Sms.MESSAGE_TYPE_SENT, 0)) { 3115 Log.w(TAG, "Failed to move " + msgInfo.uri + " to SENT"); 3116 } 3117 } else { 3118 delete = true; 3119 } 3120 3121 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msgInfo.id, 3122 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3123 sendEvent(evt); 3124 3125 } else { 3126 if (msgInfo.retry == 1) { 3127 /* Notify failure, but keep message in outbox for resending */ 3128 msgInfo.resend = true; 3129 msgInfo.partsSent = 0; // Reset counter for the retry 3130 msgInfo.failedSent = false; 3131 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id, 3132 getSmsFolderName(Sms.MESSAGE_TYPE_OUTBOX), null, mSmsType); 3133 sendEvent(evt); 3134 } else { 3135 if (msgInfo.transparent == 0) { 3136 if (!Sms.moveMessageToFolder(context, msgInfo.uri, 3137 Sms.MESSAGE_TYPE_FAILED, 0)) { 3138 Log.w(TAG, "Failed to move " + msgInfo.uri + " to FAILED"); 3139 } 3140 } else { 3141 delete = true; 3142 } 3143 3144 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id, 3145 getSmsFolderName(Sms.MESSAGE_TYPE_FAILED), null, mSmsType); 3146 sendEvent(evt); 3147 } 3148 } 3149 3150 if (delete) { 3151 /* Delete from Observer message list to avoid delete notifications */ 3152 synchronized(getMsgListSms()) { 3153 getMsgListSms().remove(msgInfo.id); 3154 } 3155 3156 /* Delete from DB */ 3157 mResolver.delete(msgInfo.uri, null, null); 3158 } 3159 } 3160 3161 private void actionMessageDelivery(Context context, Intent intent, PushMsgInfo msgInfo) { 3162 Uri messageUri = intent.getData(); 3163 msgInfo.sendInProgress = false; 3164 3165 Cursor cursor = mResolver.query(msgInfo.uri, ID_PROJECTION, null, null, null); 3166 3167 try { 3168 if (cursor.moveToFirst()) { 3169 int messageId = cursor.getInt(0); 3170 3171 Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId); 3172 3173 if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status=" 3174 + msgInfo.statusDelivered); 3175 3176 ContentValues contentValues = new ContentValues(2); 3177 3178 contentValues.put(Sms.STATUS, msgInfo.statusDelivered); 3179 contentValues.put(Inbox.DATE_SENT, System.currentTimeMillis()); 3180 mResolver.update(updateUri, contentValues, null, null); 3181 } else { 3182 Log.d(TAG, "Can't find message for status update: " + messageUri); 3183 } 3184 } finally { 3185 if (cursor != null) cursor.close(); 3186 } 3187 3188 if (msgInfo.statusDelivered == 0) { 3189 Event evt = new Event(EVENT_TYPE_DELEVERY_SUCCESS, msgInfo.id, 3190 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3191 sendEvent(evt); 3192 } else { 3193 Event evt = new Event(EVENT_TYPE_DELIVERY_FAILURE, msgInfo.id, 3194 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3195 sendEvent(evt); 3196 } 3197 3198 mPushMsgList.remove(msgInfo.id); 3199 } 3200 } 3201 3202 private class CeBroadcastReceiver extends BroadcastReceiver { 3203 public void register() { 3204 UserManager manager = UserManager.get(mContext); 3205 if (manager == null || manager.isUserUnlocked()) { 3206 mStorageUnlocked = true; 3207 return; 3208 } 3209 3210 Handler handler = new Handler(Looper.getMainLooper()); 3211 IntentFilter intentFilter = new IntentFilter(); 3212 intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); 3213 mContext.registerReceiver(this, intentFilter, null, handler); 3214 } 3215 3216 public void unregister() { 3217 try { 3218 mContext.unregisterReceiver(this); 3219 } catch (IllegalArgumentException e) { 3220 /* do nothing */ 3221 } 3222 } 3223 3224 @Override 3225 public void onReceive(Context context, Intent intent) { 3226 String action = intent.getAction(); 3227 Log.d(TAG, "onReceive: action" + action); 3228 3229 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 3230 try { 3231 initMsgList(); 3232 } catch (RemoteException e) { 3233 Log.e(TAG, "Error initializing SMS/MMS message lists."); 3234 } 3235 3236 for (String folder : FOLDER_SMS_MAP.values()) { 3237 Event evt = new Event(EVENT_TYPE_NEW, -1, folder, mSmsType); 3238 sendEvent(evt); 3239 } 3240 mStorageUnlocked = true; 3241 /* After unlock this BroadcastReceiver is never needed */ 3242 unregister(); 3243 } else { 3244 Log.d(TAG, "onReceive: Unknown action " + action); 3245 } 3246 } 3247 } 3248 3249 /** 3250 * Handle MMS sent intents in disconnected(MNS) state, where we do not need to send any 3251 * notifications. 3252 * @param context The context to use for provider operations 3253 * @param intent The intent received 3254 * @param result The result 3255 */ 3256 public static void actionMmsSent(Context context, Intent intent, int result, 3257 Map<Long, Msg> mmsMsgList) { 3258 /* 3259 * if transparent: 3260 * delete message and send notification(regardless of result) 3261 * else 3262 * Result == Success: 3263 * move to sent folder (will trigger notification) 3264 * Result == Fail: 3265 * move to outbox (send delivery fail notification) 3266 */ 3267 if(D) Log.d(TAG,"actionMmsSent()"); 3268 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3269 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3270 if(handle < 0) { 3271 Log.w(TAG, "Intent received for an invalid handle"); 3272 return; 3273 } 3274 ContentResolver resolver = context.getContentResolver(); 3275 if(transparent == 1) { 3276 /* The specification is a bit unclear about the transparent flag. If it is set 3277 * no copy of the message shall be kept in the send folder after the message 3278 * was send, but in the case of a send error, it is unclear what to do. 3279 * As it will not be transparent if we keep the message in any folder, 3280 * we delete the message regardless of the result. 3281 * If we however do have a MNS connection we need to send a notification. */ 3282 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 3283 /* Delete from observer message list to avoid delete notifications */ 3284 if(mmsMsgList != null) { 3285 synchronized(mmsMsgList) { 3286 mmsMsgList.remove(handle); 3287 } 3288 } 3289 /* Delete message */ 3290 if(D) Log.d(TAG,"Transparent in use - delete"); 3291 resolver.delete(uri, null, null); 3292 } else if (result == Activity.RESULT_OK) { 3293 /* This will trigger a notification */ 3294 moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_SENT); 3295 } else { 3296 if(mmsMsgList != null) { 3297 synchronized(mmsMsgList) { 3298 Msg msg = mmsMsgList.get(handle); 3299 if(msg != null) { 3300 msg.type=Mms.MESSAGE_BOX_OUTBOX; 3301 } 3302 } 3303 } 3304 /* Hand further retries over to the MMS application */ 3305 moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_OUTBOX); 3306 } 3307 } 3308 3309 public static void actionMessageSentDisconnected(Context context, Intent intent, int result) { 3310 TYPE type = TYPE.fromOrdinal( 3311 intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal())); 3312 if(type == TYPE.MMS) { 3313 actionMmsSent(context, intent, result, null); 3314 } else { 3315 actionSmsSentDisconnected(context, intent, result); 3316 } 3317 } 3318 3319 public static void actionSmsSentDisconnected(Context context, Intent intent, int result) { 3320 /* Check permission for message deletion. */ 3321 if ((Binder.getCallingPid() != Process.myPid()) || 3322 (context.checkCallingOrSelfPermission("android.Manifest.permission.WRITE_SMS") 3323 != PackageManager.PERMISSION_GRANTED)) { 3324 Log.w(TAG, "actionSmsSentDisconnected: Not allowed to delete SMS/MMS messages"); 3325 return; 3326 } 3327 3328 boolean delete = false; 3329 //int retry = intent.getIntExtra(EXTRA_MESSAGE_SENT_RETRY, 0); 3330 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3331 String uriString = intent.getStringExtra(EXTRA_MESSAGE_SENT_URI); 3332 if(uriString == null) { 3333 // Nothing we can do about it, just bail out 3334 return; 3335 } 3336 Uri uri = Uri.parse(uriString); 3337 3338 if (result == Activity.RESULT_OK) { 3339 Log.d(TAG, "actionMessageSentDisconnected: result OK"); 3340 if (transparent == 0) { 3341 if (!Sms.moveMessageToFolder(context, uri, 3342 Sms.MESSAGE_TYPE_SENT, 0)) { 3343 Log.d(TAG, "Failed to move " + uri + " to SENT"); 3344 } 3345 } else { 3346 delete = true; 3347 } 3348 } else { 3349 /*if (retry == 1) { 3350 The retry feature only works while connected, else we fail the send, 3351 * and move the message to failed, to let the user/app resend manually later. 3352 } else */{ 3353 if (transparent == 0) { 3354 if (!Sms.moveMessageToFolder(context, uri, 3355 Sms.MESSAGE_TYPE_FAILED, 0)) { 3356 Log.d(TAG, "Failed to move " + uri + " to FAILED"); 3357 } 3358 } else { 3359 delete = true; 3360 } 3361 } 3362 } 3363 3364 if (delete) { 3365 /* Delete from DB */ 3366 ContentResolver resolver = context.getContentResolver(); 3367 if (resolver != null) { 3368 resolver.delete(uri, null, null); 3369 } else { 3370 Log.w(TAG, "Unable to get resolver"); 3371 } 3372 } 3373 } 3374 3375 private void registerPhoneServiceStateListener() { 3376 TelephonyManager tm = (TelephonyManager)mContext.getSystemService( 3377 Context.TELEPHONY_SERVICE); 3378 tm.listen(mPhoneListener, PhoneStateListener.LISTEN_SERVICE_STATE); 3379 } 3380 3381 private void unRegisterPhoneServiceStateListener() { 3382 TelephonyManager tm = (TelephonyManager)mContext.getSystemService( 3383 Context.TELEPHONY_SERVICE); 3384 tm.listen(mPhoneListener, PhoneStateListener.LISTEN_NONE); 3385 } 3386 3387 private void resendPendingMessages() { 3388 /* Send pending messages in outbox */ 3389 String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX; 3390 UserManager manager = UserManager.get(mContext); 3391 if (manager == null || !manager.isUserUnlocked()) return; 3392 Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, 3393 null); 3394 try { 3395 if (c != null && c.moveToFirst()) { 3396 do { 3397 long id = c.getLong(c.getColumnIndex(Sms._ID)); 3398 String msgBody = c.getString(c.getColumnIndex(Sms.BODY)); 3399 PushMsgInfo msgInfo = mPushMsgList.get(id); 3400 if (msgInfo == null || !msgInfo.resend || 3401 msgInfo.sendInProgress) { 3402 continue; 3403 } 3404 msgInfo.sendInProgress = true; 3405 sendMessage(msgInfo, msgBody); 3406 } while (c.moveToNext()); 3407 } 3408 } finally { 3409 if (c != null) c.close(); 3410 } 3411 3412 3413 } 3414 3415 private void failPendingMessages() { 3416 /* Move pending messages from outbox to failed */ 3417 String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX; 3418 Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, 3419 null); 3420 try { 3421 if (c != null && c.moveToFirst()) { 3422 do { 3423 long id = c.getLong(c.getColumnIndex(Sms._ID)); 3424 String msgBody = c.getString(c.getColumnIndex(Sms.BODY)); 3425 PushMsgInfo msgInfo = mPushMsgList.get(id); 3426 if (msgInfo == null || !msgInfo.resend) { 3427 continue; 3428 } 3429 Sms.moveMessageToFolder(mContext, msgInfo.uri, 3430 Sms.MESSAGE_TYPE_FAILED, 0); 3431 } while (c.moveToNext()); 3432 } 3433 } finally { 3434 if (c != null) c.close(); 3435 } 3436 3437 } 3438 3439 private void removeDeletedMessages() { 3440 /* Remove messages from virtual "deleted" folder (thread_id -1) */ 3441 mResolver.delete(Sms.CONTENT_URI, 3442 "thread_id = " + DELETED_THREAD_ID, null); 3443 } 3444 3445 private PhoneStateListener mPhoneListener = new PhoneStateListener() { 3446 @Override 3447 public void onServiceStateChanged(ServiceState serviceState) { 3448 Log.d(TAG, "Phone service state change: " + serviceState.getState()); 3449 if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) { 3450 resendPendingMessages(); 3451 } 3452 } 3453 }; 3454 3455 public void init() { 3456 if (mSmsBroadcastReceiver != null) { 3457 mSmsBroadcastReceiver.register(); 3458 } 3459 3460 if (mCeBroadcastReceiver != null) { 3461 mCeBroadcastReceiver.register(); 3462 } 3463 3464 registerPhoneServiceStateListener(); 3465 mInitialized = true; 3466 } 3467 3468 public void deinit() { 3469 mInitialized = false; 3470 unregisterObserver(); 3471 if (mSmsBroadcastReceiver != null) { 3472 mSmsBroadcastReceiver.unregister(); 3473 } 3474 unRegisterPhoneServiceStateListener(); 3475 failPendingMessages(); 3476 removeDeletedMessages(); 3477 } 3478 3479 public boolean handleSmsSendIntent(Context context, Intent intent){ 3480 TYPE type = TYPE.fromOrdinal( 3481 intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal())); 3482 if(type == TYPE.MMS) { 3483 return handleMmsSendIntent(context, intent); 3484 } else { 3485 if(mInitialized) { 3486 mSmsBroadcastReceiver.onReceive(context, intent); 3487 return true; 3488 } 3489 } 3490 return false; 3491 } 3492 3493 public boolean handleMmsSendIntent(Context context, Intent intent){ 3494 if(D) Log.w(TAG, "handleMmsSendIntent()"); 3495 if(!mMnsClient.isConnected()) { 3496 // No need to handle notifications, just use default handling 3497 if(D) Log.w(TAG, "MNS not connected - use static handling"); 3498 return false; 3499 } 3500 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3501 int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED); 3502 actionMmsSent(context, intent, result, getMsgListMms()); 3503 if(handle < 0) { 3504 Log.w(TAG, "Intent received for an invalid handle"); 3505 return true; 3506 } 3507 if(result != Activity.RESULT_OK) { 3508 if(mObserverRegistered) { 3509 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, handle, 3510 getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS); 3511 sendEvent(evt); 3512 } 3513 } else { 3514 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3515 if(transparent != 0) { 3516 if(mObserverRegistered) { 3517 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, handle, 3518 getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS); 3519 sendEvent(evt); 3520 } 3521 } 3522 } 3523 return true; 3524 } 3525 3526} 3527