BluetoothMapContentObserver.java revision 2fbb1d97d08d5d72fe824e543c714e56cd7be236
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.SmsMessage; 51import android.telephony.TelephonyManager; 52import android.text.format.DateUtils; 53import android.text.TextUtils; 54import android.util.Log; 55import android.util.Xml; 56 57import org.xmlpull.v1.XmlSerializer; 58 59import com.android.bluetooth.map.BluetoothMapUtils.TYPE; 60import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart; 61import com.android.bluetooth.mapapi.BluetoothMapContract; 62import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns; 63import com.google.android.mms.pdu.PduHeaders; 64 65import java.io.FileNotFoundException; 66import java.io.FileOutputStream; 67import java.io.IOException; 68import java.io.OutputStream; 69import java.io.StringWriter; 70import java.io.UnsupportedEncodingException; 71import java.util.ArrayList; 72import java.util.Arrays; 73import java.util.Calendar; 74import java.util.Collections; 75import java.util.HashMap; 76import java.util.HashSet; 77import java.util.Map; 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 public Event(String eventType, long handle, String folder, 638 String oldFolder, TYPE msgType) { 639 this.eventType = eventType; 640 this.handle = handle; 641 setFolderPath(folder, msgType); 642 if (oldFolder != null) { 643 if(msgType == TYPE.EMAIL || msgType == TYPE.IM) { 644 this.oldFolder = oldFolder; 645 } else { 646 this.oldFolder = PATH + oldFolder; 647 } 648 } else { 649 this.oldFolder = null; 650 } 651 this.msgType = msgType; 652 } 653 654 public Event(String eventType, long handle, String folder, TYPE msgType) { 655 this.eventType = eventType; 656 this.handle = handle; 657 setFolderPath(folder, msgType); 658 this.msgType = msgType; 659 } 660 661 /* extended event type 1.1 */ 662 public Event(String eventType, long handle, String folder, TYPE msgType, 663 String datetime, String subject, String senderName, String priority) { 664 this.eventType = eventType; 665 this.handle = handle; 666 setFolderPath(folder, msgType); 667 this.msgType = msgType; 668 this.datetime = datetime; 669 if (subject != null) { 670 this.subject = BluetoothMapUtils.stripInvalidChars(subject); 671 } 672 if (senderName != null) { 673 this.senderName = BluetoothMapUtils.stripInvalidChars(senderName); 674 } 675 this.priority = priority; 676 } 677 678 /* extended event type 1.2 message events */ 679 public Event(String eventType, long handle, String folder, TYPE msgType, 680 String datetime, String subject, String senderName, String priority, 681 long conversationID, String conversationName) { 682 this.eventType = eventType; 683 this.handle = handle; 684 setFolderPath(folder, msgType); 685 this.msgType = msgType; 686 this.datetime = datetime; 687 if (subject != null) { 688 this.subject = BluetoothMapUtils.stripInvalidChars(subject); 689 } 690 if (senderName != null) { 691 this.senderName = BluetoothMapUtils.stripInvalidChars(senderName); 692 } 693 if (conversationID != 0) { 694 this.conversationID = conversationID; 695 } 696 if (conversationName != null) { 697 this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName); 698 } 699 this.priority = priority; 700 } 701 702 /* extended event type 1.2 for conversation, presence or chat state changed events */ 703 public Event(String eventType, String uci, TYPE msgType, String name, String priority, 704 String lastActivity, long conversationID, String conversationName, 705 int presenceState, String presenceStatus, int chatState) { 706 this.eventType = eventType; 707 this.uci = uci; 708 this.msgType = msgType; 709 if (name != null) { 710 this.senderName = BluetoothMapUtils.stripInvalidChars(name); 711 } 712 this.priority = priority; 713 this.datetime = lastActivity; 714 if (conversationID != 0) { 715 this.conversationID = conversationID; 716 } 717 if (conversationName != null) { 718 this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName); 719 } 720 if (presenceState != BluetoothMapContract.PresenceState.UNKNOWN) { 721 this.presenceState = presenceState; 722 } 723 if (presenceStatus != null) { 724 this.presenceStatus = BluetoothMapUtils.stripInvalidChars(presenceStatus); 725 } 726 if (chatState != BluetoothMapContract.ChatState.UNKNOWN) { 727 this.chatState = chatState; 728 } 729 } 730 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 public Msg(long id, int type, int threadId, int readFlag) { 846 this.id = id; 847 this.type = type; 848 this.threadId = threadId; 849 this.flagRead = readFlag; 850 } 851 public Msg(long id, long folderId, int readFlag) { 852 this.id = id; 853 this.folderId = folderId; 854 this.flagRead = readFlag; 855 } 856 857 /* Eclipse generated hashCode() and equals() to make 858 * hashMap lookup work independent of whether the obj 859 * is used for email or SMS/MMS and whether or not the 860 * oldFolder is set. */ 861 @Override 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(evt.eventType == EVENT_TYPE_NEW) { 1121 if(!sendEventNewMessage(eventFilter)) { 1122 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1123 return; 1124 } 1125 } else if(evt.eventType == EVENT_TYPE_DELETE) { 1126 if(!sendEventMessageDeleted(eventFilter)) { 1127 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1128 return; 1129 } 1130 } else if(evt.eventType == EVENT_TYPE_REMOVED) { 1131 if(!sendEventMessageRemoved(eventFilter)) { 1132 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1133 return; 1134 } 1135 } else if(evt.eventType == EVENT_TYPE_SHIFT) { 1136 if(!sendEventMessageShift(eventFilter)) { 1137 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1138 return; 1139 } 1140 } else if(evt.eventType == EVENT_TYPE_DELEVERY_SUCCESS) { 1141 if(!sendEventDeliverySuccess(eventFilter)) { 1142 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1143 return; 1144 } 1145 } else if(evt.eventType == EVENT_TYPE_SENDING_SUCCESS) { 1146 if(!sendEventSendingSuccess(eventFilter)) { 1147 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1148 return; 1149 } 1150 } else if(evt.eventType == EVENT_TYPE_SENDING_FAILURE) { 1151 if(!sendEventSendingFailed(eventFilter)) { 1152 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1153 return; 1154 } 1155 } else if(evt.eventType == EVENT_TYPE_DELIVERY_FAILURE) { 1156 if(!sendEventDeliveryFailed(eventFilter)) { 1157 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1158 return; 1159 } 1160 } else if(evt.eventType == EVENT_TYPE_READ_STATUS) { 1161 if(!sendEventReadStatusChanged(eventFilter)) { 1162 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1163 return; 1164 } 1165 } else if(evt.eventType == EVENT_TYPE_CONVERSATION) { 1166 if(!sendEventConversationChanged(eventFilter)) { 1167 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1168 return; 1169 } 1170 } else if(evt.eventType == EVENT_TYPE_PRESENCE) { 1171 if(!sendEventParticipantPresenceChanged(eventFilter)) { 1172 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1173 return; 1174 } 1175 } else if(evt.eventType == EVENT_TYPE_CHAT_STATE) { 1176 if(!sendEventParticipantChatstateChanged(eventFilter)) { 1177 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType); 1178 return; 1179 } 1180 } 1181 1182 try { 1183 mMnsClient.sendEvent(evt.encode(), mMasId); 1184 } catch (UnsupportedEncodingException ex) { 1185 /* do nothing */ 1186 if (D) Log.e(TAG, "Exception - should not happen: ",ex); 1187 } 1188 } 1189 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 || currentPresenceStatus != presenceStatus) { 1984 long lastOnline = c 1985 .getLong(cInfo.mContactColLastOnline); 1986 contact.setPresenceAvailability(presenceState); 1987 contact.setLastActivity(lastOnline); 1988 if (currentPresenceStatus != null 1989 && !currentPresenceStatus 1990 .equals(presenceStatus)) { 1991 contact.setPresenceStatus(presenceStatus); 1992 } 1993 Event evt = new Event( 1994 EVENT_TYPE_PRESENCE, 1995 uci, 1996 mAccount.getType(), 1997 contact.getName(), 1998 String.valueOf(contact 1999 .getPriority()), 2000 BluetoothMapUtils 2001 .getDateTimeString(lastOnline), 2002 convoId, convoName, 2003 presenceState, presenceStatus, 2004 0); 2005 sendEvent(evt); 2006 } 2007 2008 // Check if chat state is updated 2009 int chatState = c.getInt(cInfo.mContactColChatState); 2010 if (contact.getChatState() != chatState) { 2011 // Get DB timestamp 2012 long lastActivity = c.getLong(cInfo.mContactColLastActive); 2013 contact.setLastActivity(lastActivity); 2014 contact.setChatState(chatState); 2015 Event evt = new Event( 2016 EVENT_TYPE_CHAT_STATE, 2017 uci, 2018 mAccount.getType(), 2019 contact.getName(), 2020 String.valueOf(contact 2021 .getPriority()), 2022 BluetoothMapUtils 2023 .getDateTimeString(lastActivity), 2024 convoId, convoName, 0, null, 2025 chatState); 2026 sendEvent(evt); 2027 } 2028 contactList.put(uci, contact); 2029 } 2030 } while (c.moveToNext()); 2031 } 2032 if(getContactList().size() > 0) { 2033 // one or more contacts were deleted, hence the conversation listing 2034 // version counter should change. 2035 listChanged = true; 2036 } 2037 setContactList(contactList, listChanged); 2038 } // end synchronized 2039 } finally { 2040 if (c != null) c.close(); 2041 } 2042 } catch (RemoteException e) { 2043 mMasInstance.restartObexServerSession(); 2044 Log.w(TAG, 2045 "Problems contacting the ContentProvider in mas Instance " 2046 + mMasId + " restaring ObexServerSession"); 2047 } 2048 2049 } 2050 // TODO: conversation contact updates if IM and SMS(MMS in one instance 2051 } 2052 2053 private boolean setEmailMessageStatusDelete(BluetoothMapFolderElement mCurrentFolder, 2054 String uriStr, long handle, int status) { 2055 boolean res = false; 2056 Uri uri = Uri.parse(uriStr + BluetoothMapContract.TABLE_MESSAGE); 2057 2058 int updateCount = 0; 2059 ContentValues contentValues = new ContentValues(); 2060 BluetoothMapFolderElement deleteFolder = mFolders. 2061 getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED); 2062 contentValues.put(BluetoothMapContract.MessageColumns._ID, handle); 2063 synchronized(getMsgListMsg()) { 2064 Msg msg = getMsgListMsg().get(handle); 2065 if (status == BluetoothMapAppParams.STATUS_VALUE_YES) { 2066 /* Set deleted folder id */ 2067 long folderId = -1; 2068 if(deleteFolder != null) { 2069 folderId = deleteFolder.getFolderId(); 2070 } 2071 contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID,folderId); 2072 updateCount = mResolver.update(uri, contentValues, null, null); 2073 /* The race between updating the value in our cached values and the database 2074 * is handled by the synchronized statement. */ 2075 if(updateCount > 0) { 2076 res = true; 2077 if (msg != null) { 2078 msg.oldFolderId = msg.folderId; 2079 /* Update the folder ID to avoid triggering an event for MCE 2080 * initiated actions. */ 2081 msg.folderId = folderId; 2082 } 2083 if(D) Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId); 2084 } else { 2085 Log.w(TAG, "Msg: " + handle + " - Set delete status " + status 2086 + " failed for folderId " + folderId); 2087 } 2088 } else if (status == BluetoothMapAppParams.STATUS_VALUE_NO) { 2089 /* Undelete message. move to old folder if we know it, 2090 * else move to inbox - as dictated by the spec. */ 2091 if(msg != null && deleteFolder != null && 2092 msg.folderId == deleteFolder.getFolderId()) { 2093 /* Only modify messages in the 'Deleted' folder */ 2094 long folderId = -1; 2095 BluetoothMapFolderElement inboxFolder = mCurrentFolder. 2096 getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX); 2097 if (msg != null && msg.oldFolderId != -1) { 2098 folderId = msg.oldFolderId; 2099 } else { 2100 if(inboxFolder != null) { 2101 folderId = inboxFolder.getFolderId(); 2102 } 2103 if(D)Log.d(TAG,"We did not delete the message, hence the old folder " + 2104 "is unknown. Moving to inbox."); 2105 } 2106 contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId); 2107 updateCount = mResolver.update(uri, contentValues, null, null); 2108 if(updateCount > 0) { 2109 res = true; 2110 /* Update the folder ID to avoid triggering an event for MCE 2111 * initiated actions. */ 2112 /* UPDATE: Actually the BT-Spec. states that an undelete is a move of the 2113 * message to INBOX - clearified in errata 5591. 2114 * Therefore we update the cache to INBOX-folderId - to trigger a message 2115 * shift event to the old-folder. */ 2116 if(inboxFolder != null) { 2117 msg.folderId = inboxFolder.getFolderId(); 2118 } else { 2119 msg.folderId = folderId; 2120 } 2121 } else { 2122 if(D)Log.d(TAG,"We did not delete the message, hence the old folder " + 2123 "is unknown. Moving to inbox."); 2124 } 2125 } 2126 } 2127 if(V) { 2128 BluetoothMapFolderElement folderElement; 2129 String folderName = "unknown"; 2130 if (msg != null) { 2131 folderElement = mCurrentFolder.getFolderById(msg.folderId); 2132 if(folderElement != null) { 2133 folderName = folderElement.getName(); 2134 } 2135 } 2136 Log.d(TAG,"setEmailMessageStatusDelete: " + handle + " from " + folderName 2137 + " status: " + status); 2138 } 2139 } 2140 if(!res) { 2141 Log.w(TAG, "Set delete status " + status + " failed."); 2142 } 2143 return res; 2144 } 2145 2146 private void updateThreadId(Uri uri, String valueString, long threadId) { 2147 ContentValues contentValues = new ContentValues(); 2148 contentValues.put(valueString, threadId); 2149 mResolver.update(uri, contentValues, null, null); 2150 } 2151 2152 private boolean deleteMessageMms(long handle) { 2153 boolean res = false; 2154 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 2155 Cursor c = mResolver.query(uri, null, null, null, null); 2156 try { 2157 if (c != null && c.moveToFirst()) { 2158 /* Move to deleted folder, or delete if already in deleted folder */ 2159 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 2160 if (threadId != DELETED_THREAD_ID) { 2161 /* Set deleted thread id */ 2162 synchronized(getMsgListMms()) { 2163 Msg msg = getMsgListMms().get(handle); 2164 if(msg != null) { // This will always be the case 2165 msg.threadId = DELETED_THREAD_ID; 2166 } 2167 } 2168 updateThreadId(uri, Mms.THREAD_ID, DELETED_THREAD_ID); 2169 } else { 2170 /* Delete from observer message list to avoid delete notifications */ 2171 synchronized(getMsgListMms()) { 2172 getMsgListMms().remove(handle); 2173 } 2174 /* Delete message */ 2175 mResolver.delete(uri, null, null); 2176 } 2177 res = true; 2178 } 2179 } finally { 2180 if (c != null) c.close(); 2181 } 2182 2183 return res; 2184 } 2185 2186 private boolean unDeleteMessageMms(long handle) { 2187 boolean res = false; 2188 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 2189 Cursor c = mResolver.query(uri, null, null, null, null); 2190 try { 2191 if (c != null && c.moveToFirst()) { 2192 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 2193 if (threadId == DELETED_THREAD_ID) { 2194 /* Restore thread id from address, or if no thread for address 2195 * create new thread by insert and remove of fake message */ 2196 String address; 2197 long id = c.getLong(c.getColumnIndex(Mms._ID)); 2198 int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 2199 if (msgBox == Mms.MESSAGE_BOX_INBOX) { 2200 address = BluetoothMapContent.getAddressMms(mResolver, id, 2201 BluetoothMapContent.MMS_FROM); 2202 } else { 2203 address = BluetoothMapContent.getAddressMms(mResolver, id, 2204 BluetoothMapContent.MMS_TO); 2205 } 2206 Set<String> recipients = new HashSet<String>(); 2207 recipients.addAll(Arrays.asList(address)); 2208 Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients); 2209 synchronized(getMsgListMms()) { 2210 Msg msg = getMsgListMms().get(handle); 2211 if(msg != null) { // This will always be the case 2212 msg.threadId = oldThreadId.intValue(); 2213 // Spec. states that undelete shall shift the message to Inbox. 2214 // Hence we need to trigger a message shift from INBOX to old-folder 2215 // after undelete. 2216 // We do this by changing the cached folder value to being inbox - hence 2217 // the event handler will se the update as the message have been shifted 2218 // from INBOX to old-folder. (Errata 5591 clearifies this) 2219 msg.type = Mms.MESSAGE_BOX_INBOX; 2220 } 2221 } 2222 updateThreadId(uri, Mms.THREAD_ID, oldThreadId); 2223 } else { 2224 Log.d(TAG, "Message not in deleted folder: handle " + handle 2225 + " threadId " + threadId); 2226 } 2227 res = true; 2228 } 2229 } finally { 2230 if (c != null) c.close(); 2231 } 2232 return res; 2233 } 2234 2235 private boolean deleteMessageSms(long handle) { 2236 boolean res = false; 2237 Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle); 2238 Cursor c = mResolver.query(uri, null, null, null, null); 2239 try { 2240 if (c != null && c.moveToFirst()) { 2241 /* Move to deleted folder, or delete if already in deleted folder */ 2242 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 2243 if (threadId != DELETED_THREAD_ID) { 2244 synchronized(getMsgListSms()) { 2245 Msg msg = getMsgListSms().get(handle); 2246 if(msg != null) { // This will always be the case 2247 msg.threadId = DELETED_THREAD_ID; 2248 } 2249 } 2250 /* Set deleted thread id */ 2251 updateThreadId(uri, Sms.THREAD_ID, DELETED_THREAD_ID); 2252 } else { 2253 /* Delete from observer message list to avoid delete notifications */ 2254 synchronized(getMsgListSms()) { 2255 getMsgListSms().remove(handle); 2256 } 2257 /* Delete message */ 2258 mResolver.delete(uri, null, null); 2259 } 2260 res = true; 2261 } 2262 } finally { 2263 if (c != null) c.close(); 2264 } 2265 return res; 2266 } 2267 2268 private boolean unDeleteMessageSms(long handle) { 2269 boolean res = false; 2270 Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle); 2271 Cursor c = mResolver.query(uri, null, null, null, null); 2272 try { 2273 if (c != null && c.moveToFirst()) { 2274 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 2275 if (threadId == DELETED_THREAD_ID) { 2276 String address = c.getString(c.getColumnIndex(Sms.ADDRESS)); 2277 Set<String> recipients = new HashSet<String>(); 2278 recipients.addAll(Arrays.asList(address)); 2279 Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients); 2280 synchronized(getMsgListSms()) { 2281 Msg msg = getMsgListSms().get(handle); 2282 if(msg != null) { 2283 msg.threadId = oldThreadId.intValue(); 2284 /* This will always be the case 2285 * The threadId is specified as an int, so it is safe to truncate 2286 * TODO: Test that this will trigger a message-shift from Inbox 2287 * to old-folder 2288 **/ 2289 /* Spec. states that undelete shall shift the message to Inbox. 2290 * Hence we need to trigger a message shift from INBOX to old-folder 2291 * after undelete. 2292 * We do this by changing the cached folder value to being inbox - hence 2293 * the event handler will se the update as the message have been shifted 2294 * from INBOX to old-folder. (Errata 5591 clearifies this) 2295 * */ 2296 msg.type = Sms.MESSAGE_TYPE_INBOX; 2297 } 2298 } 2299 updateThreadId(uri, Sms.THREAD_ID, oldThreadId); 2300 } else { 2301 Log.d(TAG, "Message not in deleted folder: handle " + handle 2302 + " threadId " + threadId); 2303 } 2304 res = true; 2305 } 2306 } finally { 2307 if (c != null) c.close(); 2308 } 2309 return res; 2310 } 2311 2312 /** 2313 * 2314 * @param handle 2315 * @param type 2316 * @param mCurrentFolder 2317 * @param uriStr 2318 * @param statusValue 2319 * @return true is success 2320 */ 2321 public boolean setMessageStatusDeleted(long handle, TYPE type, 2322 BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue) { 2323 boolean res = false; 2324 if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle 2325 + " type " + type + " value " + statusValue); 2326 2327 if (type == TYPE.EMAIL) { 2328 res = setEmailMessageStatusDelete(mCurrentFolder, uriStr, handle, statusValue); 2329 } else if (type == TYPE.IM) { 2330 // TODO: to do when deleting IM message 2331 if (D) Log.d(TAG, "setMessageStatusDeleted: IM not handled" ); 2332 } else { 2333 if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) { 2334 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) { 2335 res = deleteMessageSms(handle); 2336 } else if (type == TYPE.MMS) { 2337 res = deleteMessageMms(handle); 2338 } 2339 } else if (statusValue == BluetoothMapAppParams.STATUS_VALUE_NO) { 2340 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) { 2341 res = unDeleteMessageSms(handle); 2342 } else if (type == TYPE.MMS) { 2343 res = unDeleteMessageMms(handle); 2344 } 2345 } 2346 } 2347 return res; 2348 } 2349 2350 /** 2351 * 2352 * @param handle 2353 * @param type 2354 * @param uriStr 2355 * @param statusValue 2356 * @return true at success 2357 */ 2358 public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue) 2359 throws RemoteException{ 2360 int count = 0; 2361 2362 if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle 2363 + " type " + type + " value " + statusValue); 2364 2365 /* Approved MAP spec errata 3445 states that read status initiated 2366 * by the MCE shall change the MSE read status. */ 2367 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) { 2368 Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle); 2369 ContentValues contentValues = new ContentValues(); 2370 contentValues.put(Sms.READ, statusValue); 2371 contentValues.put(Sms.SEEN, statusValue); 2372 String values = contentValues.toString(); 2373 if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() + " values " + values); 2374 synchronized(getMsgListSms()) { 2375 Msg msg = getMsgListSms().get(handle); 2376 if(msg != null) { // This will always be the case 2377 msg.flagRead = statusValue; 2378 } 2379 } 2380 count = mResolver.update(uri, contentValues, null, null); 2381 if (D) Log.d(TAG, " -> "+count +" rows updated!"); 2382 2383 } else if (type == TYPE.MMS) { 2384 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 2385 if (D) Log.d(TAG, " -> MMS Uri: " + uri.toString()); 2386 ContentValues contentValues = new ContentValues(); 2387 contentValues.put(Mms.READ, statusValue); 2388 synchronized(getMsgListMms()) { 2389 Msg msg = getMsgListMms().get(handle); 2390 if(msg != null) { // This will always be the case 2391 msg.flagRead = statusValue; 2392 } 2393 } 2394 count = mResolver.update(uri, contentValues, null, null); 2395 if (D) Log.d(TAG, " -> "+count +" rows updated!"); 2396 } else if (type == TYPE.EMAIL || 2397 type == TYPE.IM) { 2398 Uri uri = mMessageUri; 2399 ContentValues contentValues = new ContentValues(); 2400 contentValues.put(BluetoothMapContract.MessageColumns.FLAG_READ, statusValue); 2401 contentValues.put(BluetoothMapContract.MessageColumns._ID, handle); 2402 synchronized(getMsgListMsg()) { 2403 Msg msg = getMsgListMsg().get(handle); 2404 if(msg != null) { // This will always be the case 2405 msg.flagRead = statusValue; 2406 } 2407 } 2408 count = mProviderClient.update(uri, contentValues, null, null); 2409 } 2410 2411 return (count > 0); 2412 } 2413 2414 private class PushMsgInfo { 2415 long id; 2416 int transparent; 2417 int retry; 2418 String phone; 2419 Uri uri; 2420 long timestamp; 2421 int parts; 2422 int partsSent; 2423 int partsDelivered; 2424 boolean resend; 2425 boolean sendInProgress; 2426 boolean failedSent; // Set to true if a single part sent fail is received. 2427 int statusDelivered; // Set to != 0 if a single part deliver fail is received. 2428 2429 public PushMsgInfo(long id, int transparent, 2430 int retry, String phone, Uri uri) { 2431 this.id = id; 2432 this.transparent = transparent; 2433 this.retry = retry; 2434 this.phone = phone; 2435 this.uri = uri; 2436 this.resend = false; 2437 this.sendInProgress = false; 2438 this.failedSent = false; 2439 this.statusDelivered = 0; /* Assume success */ 2440 this.timestamp = 0; 2441 }; 2442 } 2443 2444 private Map<Long, PushMsgInfo> mPushMsgList = 2445 Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>()); 2446 2447 public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement, 2448 BluetoothMapAppParams ap, String emailBaseUri) 2449 throws IllegalArgumentException, RemoteException, IOException { 2450 if (D) Log.d(TAG, "pushMessage"); 2451 ArrayList<BluetoothMapbMessage.vCard> recipientList = msg.getRecipients(); 2452 int transparent = (ap.getTransparent() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? 2453 0 : ap.getTransparent(); 2454 int retry = ap.getRetry(); 2455 int charset = ap.getCharset(); 2456 long handle = -1; 2457 long folderId = -1; 2458 2459 if (recipientList == null) { 2460 if (folderElement.getName().equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) { 2461 BluetoothMapbMessage.vCard empty = 2462 new BluetoothMapbMessage.vCard("", "", null, null, 0); 2463 recipientList = new ArrayList<BluetoothMapbMessage.vCard>(); 2464 recipientList.add(empty); 2465 Log.w(TAG, "Added empty recipient to draft message"); 2466 } else { 2467 Log.e(TAG, "Trying to send a message with no recipients"); 2468 return -1; 2469 } 2470 } 2471 2472 if ( msg.getType().equals(TYPE.EMAIL) ) { 2473 /* Write the message to the database */ 2474 String msgBody = ((BluetoothMapbMessageEmail) msg).getEmailBody(); 2475 if (V) { 2476 int length = msgBody.length(); 2477 Log.v(TAG, "pushMessage: message string length = " + length); 2478 String[] messages = msgBody.split("\r\n"); 2479 Log.v(TAG, "pushMessage: messages count=" + messages.length); 2480 for(int i = 0; i < messages.length; i++) { 2481 Log.v(TAG, "part " + i + ":" + messages[i]); 2482 } 2483 } 2484 FileOutputStream os = null; 2485 ParcelFileDescriptor fdOut = null; 2486 Uri uriInsert = Uri.parse(emailBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2487 if (D) Log.d(TAG, "pushMessage - uriInsert= " + uriInsert.toString() + 2488 ", intoFolder id=" + folderElement.getFolderId()); 2489 2490 synchronized(getMsgListMsg()) { 2491 // Now insert the empty message into folder 2492 ContentValues values = new ContentValues(); 2493 folderId = folderElement.getFolderId(); 2494 values.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId); 2495 Uri uriNew = mProviderClient.insert(uriInsert, values); 2496 if (D) Log.d(TAG, "pushMessage - uriNew= " + uriNew.toString()); 2497 handle = Long.parseLong(uriNew.getLastPathSegment()); 2498 2499 try { 2500 fdOut = mProviderClient.openFile(uriNew, "w"); 2501 os = new FileOutputStream(fdOut.getFileDescriptor()); 2502 // Write Email to DB 2503 os.write(msgBody.getBytes(), 0, msgBody.getBytes().length); 2504 } catch (FileNotFoundException e) { 2505 Log.w(TAG, e); 2506 throw(new IOException("Unable to open file stream")); 2507 } catch (NullPointerException e) { 2508 Log.w(TAG, e); 2509 throw(new IllegalArgumentException("Unable to parse message.")); 2510 } finally { 2511 try { 2512 if(os != null) 2513 os.close(); 2514 } catch (IOException e) {Log.w(TAG, e);} 2515 try { 2516 if(fdOut != null) 2517 fdOut.close(); 2518 } catch (IOException e) {Log.w(TAG, e);} 2519 } 2520 2521 /* Extract the data for the inserted message, and store in local mirror, to 2522 * avoid sending a NewMessage Event. */ 2523 /*TODO: We need to add the new 1.1 parameter as well:-) e.g. read*/ 2524 Msg newMsg = new Msg(handle, folderId, 1); // TODO: Create define for read-state 2525 newMsg.transparent = transparent == 1; 2526 if ( folderId == folderElement.getFolderByName( 2527 BluetoothMapContract.FOLDER_NAME_OUTBOX).getFolderId() ) { 2528 newMsg.localInitiatedSend = true; 2529 } 2530 getMsgListMsg().put(handle, newMsg); 2531 } 2532 } else { // type SMS_* of MMS 2533 for (BluetoothMapbMessage.vCard recipient : recipientList) { 2534 // Only send the message to the top level recipient 2535 if(recipient.getEnvLevel() == 0) 2536 { 2537 /* Only send to first address */ 2538 String phone = recipient.getFirstPhoneNumber(); 2539 String email = recipient.getFirstEmail(); 2540 String folder = folderElement.getName(); 2541 boolean read = false; 2542 boolean deliveryReport = true; 2543 String msgBody = null; 2544 2545 /* If MMS contains text only and the size is less than ten SMS's 2546 * then convert the MMS to type SMS and then proceed 2547 */ 2548 if (msg.getType().equals(TYPE.MMS) && 2549 (((BluetoothMapbMessageMime) msg).getTextOnly())) { 2550 msgBody = ((BluetoothMapbMessageMime) msg).getMessageAsText(); 2551 SmsManager smsMng = SmsManager.getDefault(); 2552 ArrayList<String> parts = smsMng.divideMessage(msgBody); 2553 int smsParts = parts.size(); 2554 if (smsParts <= CONVERT_MMS_TO_SMS_PART_COUNT ) { 2555 if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts=" 2556 + smsParts ); 2557 msg.setType(mSmsType); 2558 } else { 2559 if (D) Log.d(TAG, "pushMessage - MMS text only but to big to " + 2560 "convert to SMS"); 2561 msgBody = null; 2562 } 2563 2564 } 2565 2566 if (msg.getType().equals(TYPE.MMS)) { 2567 /* Send message if folder is outbox else just store in draft*/ 2568 handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMime)msg, 2569 transparent, retry); 2570 } else if (msg.getType().equals(TYPE.SMS_GSM) || 2571 msg.getType().equals(TYPE.SMS_CDMA) ) { 2572 /* Add the message to the database */ 2573 if(msgBody == null) 2574 msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody(); 2575 2576 if (TextUtils.isEmpty(msgBody)) { 2577 Log.d(TAG, "PushMsg: Empty msgBody "); 2578 /* not allowed to push empty message */ 2579 throw new IllegalArgumentException("push EMPTY message: Invalid Body"); 2580 } 2581 /* We need to lock the SMS list while updating the database, 2582 * to avoid sending events on MCE initiated operation. */ 2583 Uri contentUri = Uri.parse(Sms.CONTENT_URI+ "/" + folder); 2584 Uri uri; 2585 synchronized(getMsgListSms()) { 2586 uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody, 2587 "", System.currentTimeMillis(), read, deliveryReport); 2588 2589 if(V) Log.v(TAG, "Sms.addMessageToUri() returned: " + uri); 2590 if (uri == null) { 2591 if (D) Log.d(TAG, "pushMessage - failure on add to uri " 2592 + contentUri); 2593 return -1; 2594 } 2595 Cursor c = mResolver.query(uri, SMS_PROJECTION_SHORT, null, null, null); 2596 2597 /* Extract the data for the inserted message, and store in local mirror, 2598 * to avoid sending a NewMessage Event. */ 2599 try { 2600 if (c != null && c.moveToFirst()) { 2601 long id = c.getLong(c.getColumnIndex(Sms._ID)); 2602 int type = c.getInt(c.getColumnIndex(Sms.TYPE)); 2603 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 2604 int readFlag = c.getInt(c.getColumnIndex(Sms.READ)); 2605 if(V) Log.v(TAG, "add message with id=" + id + 2606 " type=" + type + " threadId=" + threadId + 2607 " readFlag=" + readFlag + "to mMsgListSms"); 2608 Msg newMsg = new Msg(id, type, threadId, readFlag); 2609 getMsgListSms().put(id, newMsg); 2610 c.close(); 2611 } else { 2612 Log.w(TAG,"Message: " + uri + " no longer exist!"); 2613 /* This can only happen, if the message is deleted 2614 * just as it is added */ 2615 return -1; 2616 } 2617 } finally { 2618 if (c != null) c.close(); 2619 } 2620 2621 handle = Long.parseLong(uri.getLastPathSegment()); 2622 2623 /* Send message if folder is outbox */ 2624 if (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) { 2625 PushMsgInfo msgInfo = new PushMsgInfo(handle, transparent, 2626 retry, phone, uri); 2627 mPushMsgList.put(handle, msgInfo); 2628 sendMessage(msgInfo, msgBody); 2629 if(V) Log.v(TAG, "sendMessage returned..."); 2630 } /* else just added to draft */ 2631 2632 /* sendMessage causes the message to be deleted and reinserted, 2633 * hence we need to lock the list while this is happening. */ 2634 } 2635 } else { 2636 if (D) Log.d(TAG, "pushMessage - failure on type " ); 2637 return -1; 2638 } 2639 } 2640 } 2641 } 2642 2643 /* If multiple recipients return handle of last */ 2644 return handle; 2645 } 2646 2647 public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMime msg, 2648 int transparent, int retry) { 2649 /* 2650 *strategy: 2651 *1) parse message into parts 2652 *if folder is outbox/drafts: 2653 *2) push message to draft 2654 *if folder is outbox: 2655 *3) move message to outbox (to trigger the mms app to add msg to pending_messages list) 2656 *4) send intent to mms app in order to wake it up. 2657 *else if folder !outbox: 2658 *1) push message to folder 2659 * */ 2660 if (folder != null && (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) 2661 || folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT))) { 2662 long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, to_address, msg); 2663 /* if invalid handle (-1) then just return the handle 2664 * - else continue sending (if folder is outbox) */ 2665 if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle && 2666 folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) { 2667 Uri btMmsUri = MmsFileProvider.CONTENT_URI.buildUpon() 2668 .appendPath(Long.toString(handle)).build(); 2669 Intent sentIntent = new Intent(ACTION_MESSAGE_SENT); 2670 // TODO: update the mmsMsgList <- done in pushMmsToFolder() but check 2671 sentIntent.setType("message/" + Long.toString(handle)); 2672 sentIntent.putExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.MMS.ordinal()); 2673 sentIntent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, handle); // needed for notification 2674 sentIntent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, transparent); 2675 sentIntent.putExtra(EXTRA_MESSAGE_SENT_RETRY, retry); 2676 //sentIntent.setDataAndNormalize(btMmsUri); 2677 PendingIntent pendingSendIntent = PendingIntent.getBroadcast(mContext, 0, 2678 sentIntent, 0); 2679 SmsManager.getDefault().sendMultimediaMessage(mContext, 2680 btMmsUri, null/*locationUrl*/, null/*configOverrides*/, 2681 pendingSendIntent); 2682 } 2683 return handle; 2684 } else { 2685 /* not allowed to push mms to anything but outbox/draft */ 2686 throw new IllegalArgumentException("Cannot push message to other " + 2687 "folders than outbox/draft"); 2688 } 2689 } 2690 2691 private void moveDraftToOutbox(long handle) { 2692 moveMmsToFolder(handle, mResolver, Mms.MESSAGE_BOX_OUTBOX); 2693 } 2694 2695 /** 2696 * Move a MMS to another folder. 2697 * @param handle the CP handle of the message to move 2698 * @param resolver the ContentResolver to use 2699 * @param folder the destination folder - use Mms.MESSAGE_BOX_xxx 2700 */ 2701 private static void moveMmsToFolder(long handle, ContentResolver resolver, int folder) { 2702 /*Move message by changing the msg_box value in the content provider database */ 2703 if (handle != -1) { 2704 String whereClause = " _id= " + handle; 2705 Uri uri = Mms.CONTENT_URI; 2706 Cursor queryResult = resolver.query(uri, null, whereClause, null, null); 2707 try { 2708 if (queryResult != null) { 2709 if (queryResult.getCount() > 0) { 2710 queryResult.moveToFirst(); 2711 ContentValues data = new ContentValues(); 2712 /* set folder to be outbox */ 2713 data.put(Mms.MESSAGE_BOX, folder); 2714 resolver.update(uri, data, whereClause, null); 2715 if (D) Log.d(TAG, "moved MMS message to " + getMmsFolderName(folder)); 2716 } 2717 } else { 2718 Log.w(TAG, "Could not move MMS message to " + getMmsFolderName(folder)); 2719 } 2720 } finally { 2721 if (queryResult != null) queryResult.close(); 2722 } 2723 } 2724 } 2725 private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMime msg) { 2726 /** 2727 * strategy: 2728 * 1) parse msg into parts + header 2729 * 2) create thread id (abuse the ease of adding an SMS to get id for thread) 2730 * 3) push parts into content://mms/parts/ table 2731 * 3) 2732 */ 2733 2734 ContentValues values = new ContentValues(); 2735 values.put(Mms.MESSAGE_BOX, folder); 2736 values.put(Mms.READ, 0); 2737 values.put(Mms.SEEN, 0); 2738 if(msg.getSubject() != null) { 2739 values.put(Mms.SUBJECT, msg.getSubject()); 2740 } else { 2741 values.put(Mms.SUBJECT, ""); 2742 } 2743 2744 if(msg.getSubject() != null && msg.getSubject().length() > 0) { 2745 values.put(Mms.SUBJECT_CHARSET, 106); 2746 } 2747 values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related"); 2748 values.put(Mms.EXPIRY, 604800); 2749 values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR); 2750 values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ); 2751 values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION); 2752 values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL); 2753 values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO); 2754 values.put(Mms.TRANSACTION_ID, "T"+ Long.toHexString(System.currentTimeMillis())); 2755 values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO); 2756 values.put(Mms.LOCKED, 0); 2757 if(msg.getTextOnly()) 2758 values.put(Mms.TEXT_ONLY, true); 2759 values.put(Mms.MESSAGE_SIZE, msg.getSize()); 2760 2761 // Get thread id 2762 Set<String> recipients = new HashSet<String>(); 2763 recipients.addAll(Arrays.asList(to_address)); 2764 values.put(Mms.THREAD_ID, Telephony.Threads.getOrCreateThreadId(mContext, recipients)); 2765 Uri uri = Mms.CONTENT_URI; 2766 2767 synchronized (getMsgListMms()) { 2768 2769 uri = mResolver.insert(uri, values); 2770 2771 if (uri == null) { 2772 // unable to insert MMS 2773 Log.e(TAG, "Unabled to insert MMS " + values + "Uri: " + uri); 2774 return -1; 2775 } 2776 /* As we already have all the values we need, we could skip the query, but 2777 doing the query ensures we get any changes made by the content provider 2778 at insert. */ 2779 Cursor c = mResolver.query(uri, MMS_PROJECTION_SHORT, null, null, null); 2780 try { 2781 if (c != null && c.moveToFirst()) { 2782 long id = c.getLong(c.getColumnIndex(Mms._ID)); 2783 int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 2784 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 2785 int readStatus = c.getInt(c.getColumnIndex(Mms.READ)); 2786 2787 /* We must filter out any actions made by the MCE. Add the new message to 2788 * the list of known messages. */ 2789 2790 Msg newMsg = new Msg(id, type, threadId, readStatus); 2791 newMsg.localInitiatedSend = true; 2792 getMsgListMms().put(id, newMsg); 2793 c.close(); 2794 } 2795 } finally { 2796 if (c != null) c.close(); 2797 } 2798 } // Done adding changes, unlock access to mMsgListMms to allow sending MMS events again 2799 2800 long handle = Long.parseLong(uri.getLastPathSegment()); 2801 if (V) Log.v(TAG, " NEW URI " + uri.toString()); 2802 2803 try { 2804 if(msg.getMimeParts() == null) { 2805 /* Perhaps this message have been deleted, and no longer have any content, 2806 * but only headers */ 2807 Log.w(TAG, "No MMS parts present..."); 2808 } else { 2809 if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size() 2810 + " parts to the data base."); 2811 for(MimePart part : msg.getMimeParts()) { 2812 int count = 0; 2813 count++; 2814 values.clear(); 2815 if(part.mContentType != null && 2816 part.mContentType.toUpperCase().contains("TEXT")) { 2817 values.put(Mms.Part.CONTENT_TYPE, "text/plain"); 2818 values.put(Mms.Part.CHARSET, 106); 2819 if(part.mPartName != null) { 2820 values.put(Mms.Part.FILENAME, part.mPartName); 2821 values.put(Mms.Part.NAME, part.mPartName); 2822 } else { 2823 values.put(Mms.Part.FILENAME, "text_" + count +".txt"); 2824 values.put(Mms.Part.NAME, "text_" + count +".txt"); 2825 } 2826 // Ensure we have "ci" set 2827 if(part.mContentId != null) { 2828 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2829 } else { 2830 if(part.mPartName != null) { 2831 values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">"); 2832 } else { 2833 values.put(Mms.Part.CONTENT_ID, "<text_" + count + ">"); 2834 } 2835 } 2836 // Ensure we have "cl" set 2837 if(part.mContentLocation != null) { 2838 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2839 } else { 2840 if(part.mPartName != null) { 2841 values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".txt"); 2842 } else { 2843 values.put(Mms.Part.CONTENT_LOCATION, "text_" + count + ".txt"); 2844 } 2845 } 2846 2847 if(part.mContentDisposition != null) { 2848 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2849 } 2850 values.put(Mms.Part.TEXT, part.getDataAsString()); 2851 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part"); 2852 uri = mResolver.insert(uri, values); 2853 if(V) Log.v(TAG, "Added TEXT part"); 2854 2855 } else if (part.mContentType != null && 2856 part.mContentType.toUpperCase().contains("SMIL")){ 2857 values.put(Mms.Part.SEQ, -1); 2858 values.put(Mms.Part.CONTENT_TYPE, "application/smil"); 2859 if(part.mContentId != null) { 2860 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2861 } else { 2862 values.put(Mms.Part.CONTENT_ID, "<smil_" + count + ">"); 2863 } 2864 if(part.mContentLocation != null) { 2865 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2866 } else { 2867 values.put(Mms.Part.CONTENT_LOCATION, "smil_" + count + ".xml"); 2868 } 2869 2870 if(part.mContentDisposition != null) 2871 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2872 values.put(Mms.Part.FILENAME, "smil.xml"); 2873 values.put(Mms.Part.NAME, "smil.xml"); 2874 values.put(Mms.Part.TEXT, new String(part.mData, "UTF-8")); 2875 2876 uri = Uri.parse(Mms.CONTENT_URI+ "/" + handle + "/part"); 2877 uri = mResolver.insert(uri, values); 2878 if (V) Log.v(TAG, "Added SMIL part"); 2879 2880 }else /*VIDEO/AUDIO/IMAGE*/ { 2881 writeMmsDataPart(handle, part, count); 2882 if (V) Log.v(TAG, "Added OTHER part"); 2883 } 2884 if (uri != null){ 2885 if (V) Log.v(TAG, "Added part with content-type: " + part.mContentType 2886 + " to Uri: " + uri.toString()); 2887 } 2888 } 2889 } 2890 } catch (UnsupportedEncodingException e) { 2891 Log.w(TAG, e); 2892 } catch (IOException e) { 2893 Log.w(TAG, e); 2894 } 2895 2896 values.clear(); 2897 values.put(Mms.Addr.CONTACT_ID, "null"); 2898 values.put(Mms.Addr.ADDRESS, "insert-address-token"); 2899 values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_FROM); 2900 values.put(Mms.Addr.CHARSET, 106); 2901 2902 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr"); 2903 uri = mResolver.insert(uri, values); 2904 if (uri != null && V){ 2905 Log.v(TAG, " NEW URI " + uri.toString()); 2906 } 2907 2908 values.clear(); 2909 values.put(Mms.Addr.CONTACT_ID, "null"); 2910 values.put(Mms.Addr.ADDRESS, to_address); 2911 values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_TO); 2912 values.put(Mms.Addr.CHARSET, 106); 2913 2914 uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr"); 2915 uri = mResolver.insert(uri, values); 2916 if (uri != null && V){ 2917 Log.v(TAG, " NEW URI " + uri.toString()); 2918 } 2919 return handle; 2920 } 2921 2922 2923 private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException{ 2924 ContentValues values = new ContentValues(); 2925 values.put(Mms.Part.MSG_ID, handle); 2926 if(part.mContentType != null) { 2927 values.put(Mms.Part.CONTENT_TYPE, part.mContentType); 2928 } else { 2929 Log.w(TAG, "MMS has no CONTENT_TYPE for part " + count); 2930 } 2931 if(part.mContentId != null) { 2932 values.put(Mms.Part.CONTENT_ID, part.mContentId); 2933 } else { 2934 if(part.mPartName != null) { 2935 values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">"); 2936 } else { 2937 values.put(Mms.Part.CONTENT_ID, "<part_" + count + ">"); 2938 } 2939 } 2940 2941 if(part.mContentLocation != null) { 2942 values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation); 2943 } else { 2944 if(part.mPartName != null) { 2945 values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".dat"); 2946 } else { 2947 values.put(Mms.Part.CONTENT_LOCATION, "part_" + count + ".dat"); 2948 } 2949 } 2950 if(part.mContentDisposition != null) 2951 values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition); 2952 if(part.mPartName != null) { 2953 values.put(Mms.Part.FILENAME, part.mPartName); 2954 values.put(Mms.Part.NAME, part.mPartName); 2955 } else { 2956 /* We must set at least one part identifier */ 2957 values.put(Mms.Part.FILENAME, "part_" + count + ".dat"); 2958 values.put(Mms.Part.NAME, "part_" + count + ".dat"); 2959 } 2960 Uri partUri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part"); 2961 Uri res = mResolver.insert(partUri, values); 2962 2963 // Add data to part 2964 OutputStream os = mResolver.openOutputStream(res); 2965 os.write(part.mData); 2966 os.close(); 2967 } 2968 2969 2970 public void sendMessage(PushMsgInfo msgInfo, String msgBody) { 2971 2972 SmsManager smsMng = SmsManager.getDefault(); 2973 ArrayList<String> parts = smsMng.divideMessage(msgBody); 2974 msgInfo.parts = parts.size(); 2975 // We add a time stamp to differentiate delivery reports from each other for resent messages 2976 msgInfo.timestamp = Calendar.getInstance().getTime().getTime(); 2977 msgInfo.partsDelivered = 0; 2978 msgInfo.partsSent = 0; 2979 2980 ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(msgInfo.parts); 2981 ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(msgInfo.parts); 2982 2983 /* We handle the SENT intent in the MAP service, as this object 2984 * is destroyed at disconnect, hence if a disconnect occur while sending 2985 * a message, there is no intent handler to move the message from outbox 2986 * to the correct folder. 2987 * The correct solution would be to create a service that will start based on 2988 * the intent, if BT is turned off. */ 2989 2990 if (parts != null && parts.size() > 0) { 2991 for (int i = 0; i < msgInfo.parts; i++) { 2992 Intent intentDelivery, intentSent; 2993 2994 intentDelivery = new Intent(ACTION_MESSAGE_DELIVERY, null); 2995 /* Add msgId and part number to ensure the intents are different, and we 2996 * thereby get an intent for each msg part. 2997 * setType is needed to create different intents for each message id/ time stamp, 2998 * as the extras are not used when comparing. */ 2999 intentDelivery.setType("message/" + Long.toString(msgInfo.id) + 3000 msgInfo.timestamp + i); 3001 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id); 3002 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, msgInfo.timestamp); 3003 PendingIntent pendingIntentDelivery = PendingIntent.getBroadcast(mContext, 0, 3004 intentDelivery, PendingIntent.FLAG_UPDATE_CURRENT); 3005 3006 intentSent = new Intent(ACTION_MESSAGE_SENT, null); 3007 /* Add msgId and part number to ensure the intents are different, and we 3008 * thereby get an intent for each msg part. 3009 * setType is needed to create different intents for each message id/ time stamp, 3010 * as the extras are not used when comparing. */ 3011 intentSent.setType("message/" + Long.toString(msgInfo.id) + msgInfo.timestamp + i); 3012 intentSent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id); 3013 intentSent.putExtra(EXTRA_MESSAGE_SENT_URI, msgInfo.uri.toString()); 3014 intentSent.putExtra(EXTRA_MESSAGE_SENT_RETRY, msgInfo.retry); 3015 intentSent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, msgInfo.transparent); 3016 3017 PendingIntent pendingIntentSent = PendingIntent.getBroadcast(mContext, 0, 3018 intentSent, PendingIntent.FLAG_UPDATE_CURRENT); 3019 3020 // We use the same pending intent for all parts, but do not set the one shot flag. 3021 deliveryIntents.add(pendingIntentDelivery); 3022 sentIntents.add(pendingIntentSent); 3023 } 3024 3025 Log.d(TAG, "sendMessage to " + msgInfo.phone); 3026 3027 smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents, 3028 deliveryIntents); 3029 } 3030 } 3031 3032 private class SmsBroadcastReceiver extends BroadcastReceiver { 3033 private final String[] ID_PROJECTION = new String[] { Sms._ID }; 3034 private final Uri UPDATE_STATUS_URI = Uri.withAppendedPath(Sms.CONTENT_URI, "/status"); 3035 3036 public void register() { 3037 Handler handler = new Handler(Looper.getMainLooper()); 3038 3039 IntentFilter intentFilter = new IntentFilter(); 3040 intentFilter.addAction(ACTION_MESSAGE_DELIVERY); 3041 try{ 3042 intentFilter.addDataType("message/*"); 3043 } catch (MalformedMimeTypeException e) { 3044 Log.e(TAG, "Wrong mime type!!!", e); 3045 } 3046 3047 mContext.registerReceiver(this, intentFilter, null, handler); 3048 } 3049 3050 public void unregister() { 3051 try { 3052 mContext.unregisterReceiver(this); 3053 } catch (IllegalArgumentException e) { 3054 /* do nothing */ 3055 } 3056 } 3057 3058 @Override 3059 public void onReceive(Context context, Intent intent) { 3060 String action = intent.getAction(); 3061 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3062 PushMsgInfo msgInfo = mPushMsgList.get(handle); 3063 3064 Log.d(TAG, "onReceive: action" + action); 3065 3066 if (msgInfo == null) { 3067 Log.d(TAG, "onReceive: no msgInfo found for handle " + handle); 3068 return; 3069 } 3070 3071 if (action.equals(ACTION_MESSAGE_SENT)) { 3072 int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, 3073 Activity.RESULT_CANCELED); 3074 msgInfo.partsSent++; 3075 if(result != Activity.RESULT_OK) { 3076 /* If just one of the parts in the message fails, we need to send the 3077 * entire message again 3078 */ 3079 msgInfo.failedSent = true; 3080 } 3081 if(D) Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent 3082 + ", msgInfo.parts = " + msgInfo.parts + " result = " + result); 3083 3084 if (msgInfo.partsSent == msgInfo.parts) { 3085 actionMessageSent(context, intent, msgInfo); 3086 } 3087 } else if (action.equals(ACTION_MESSAGE_DELIVERY)) { 3088 long timestamp = intent.getLongExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, 0); 3089 int status = -1; 3090 if(msgInfo.timestamp == timestamp) { 3091 msgInfo.partsDelivered++; 3092 } 3093 } else { 3094 Log.d(TAG, "onReceive: Unknown action " + action); 3095 } 3096 } 3097 3098 private void actionMessageSent(Context context, Intent intent, PushMsgInfo msgInfo) { 3099 /* As the MESSAGE_SENT intent is forwarded from the MAP service, we use the intent 3100 * to carry the result, as getResult() will not return the correct value. 3101 */ 3102 boolean delete = false; 3103 3104 if(D) Log.d(TAG,"actionMessageSent(): msgInfo.failedSent = " + msgInfo.failedSent); 3105 3106 msgInfo.sendInProgress = false; 3107 3108 if (!msgInfo.failedSent) { 3109 if(D) Log.d(TAG, "actionMessageSent: result OK"); 3110 if (msgInfo.transparent == 0) { 3111 if (!Sms.moveMessageToFolder(context, msgInfo.uri, 3112 Sms.MESSAGE_TYPE_SENT, 0)) { 3113 Log.w(TAG, "Failed to move " + msgInfo.uri + " to SENT"); 3114 } 3115 } else { 3116 delete = true; 3117 } 3118 3119 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msgInfo.id, 3120 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3121 sendEvent(evt); 3122 3123 } else { 3124 if (msgInfo.retry == 1) { 3125 /* Notify failure, but keep message in outbox for resending */ 3126 msgInfo.resend = true; 3127 msgInfo.partsSent = 0; // Reset counter for the retry 3128 msgInfo.failedSent = false; 3129 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id, 3130 getSmsFolderName(Sms.MESSAGE_TYPE_OUTBOX), null, mSmsType); 3131 sendEvent(evt); 3132 } else { 3133 if (msgInfo.transparent == 0) { 3134 if (!Sms.moveMessageToFolder(context, msgInfo.uri, 3135 Sms.MESSAGE_TYPE_FAILED, 0)) { 3136 Log.w(TAG, "Failed to move " + msgInfo.uri + " to FAILED"); 3137 } 3138 } else { 3139 delete = true; 3140 } 3141 3142 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id, 3143 getSmsFolderName(Sms.MESSAGE_TYPE_FAILED), null, mSmsType); 3144 sendEvent(evt); 3145 } 3146 } 3147 3148 if (delete) { 3149 /* Delete from Observer message list to avoid delete notifications */ 3150 synchronized(getMsgListSms()) { 3151 getMsgListSms().remove(msgInfo.id); 3152 } 3153 3154 /* Delete from DB */ 3155 mResolver.delete(msgInfo.uri, null, null); 3156 } 3157 } 3158 3159 private void actionMessageDelivery(Context context, Intent intent, PushMsgInfo msgInfo) { 3160 Uri messageUri = intent.getData(); 3161 msgInfo.sendInProgress = false; 3162 3163 Cursor cursor = mResolver.query(msgInfo.uri, ID_PROJECTION, null, null, null); 3164 3165 try { 3166 if (cursor.moveToFirst()) { 3167 int messageId = cursor.getInt(0); 3168 3169 Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId); 3170 3171 if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status=" 3172 + msgInfo.statusDelivered); 3173 3174 ContentValues contentValues = new ContentValues(2); 3175 3176 contentValues.put(Sms.STATUS, msgInfo.statusDelivered); 3177 contentValues.put(Inbox.DATE_SENT, System.currentTimeMillis()); 3178 mResolver.update(updateUri, contentValues, null, null); 3179 } else { 3180 Log.d(TAG, "Can't find message for status update: " + messageUri); 3181 } 3182 } finally { 3183 if (cursor != null) cursor.close(); 3184 } 3185 3186 if (msgInfo.statusDelivered == 0) { 3187 Event evt = new Event(EVENT_TYPE_DELEVERY_SUCCESS, msgInfo.id, 3188 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3189 sendEvent(evt); 3190 } else { 3191 Event evt = new Event(EVENT_TYPE_DELIVERY_FAILURE, msgInfo.id, 3192 getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType); 3193 sendEvent(evt); 3194 } 3195 3196 mPushMsgList.remove(msgInfo.id); 3197 } 3198 } 3199 3200 private class CeBroadcastReceiver extends BroadcastReceiver { 3201 public void register() { 3202 UserManager manager = UserManager.get(mContext); 3203 if (manager == null || manager.isUserUnlocked()) { 3204 mStorageUnlocked = true; 3205 return; 3206 } 3207 3208 Handler handler = new Handler(Looper.getMainLooper()); 3209 IntentFilter intentFilter = new IntentFilter(); 3210 intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); 3211 mContext.registerReceiver(this, intentFilter, null, handler); 3212 } 3213 3214 public void unregister() { 3215 try { 3216 mContext.unregisterReceiver(this); 3217 } catch (IllegalArgumentException e) { 3218 /* do nothing */ 3219 } 3220 } 3221 3222 @Override 3223 public void onReceive(Context context, Intent intent) { 3224 String action = intent.getAction(); 3225 Log.d(TAG, "onReceive: action" + action); 3226 3227 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 3228 try { 3229 initMsgList(); 3230 } catch (RemoteException e) { 3231 Log.e(TAG, "Error initializing SMS/MMS message lists."); 3232 } 3233 3234 for (String folder : FOLDER_SMS_MAP.values()) { 3235 Event evt = new Event(EVENT_TYPE_NEW, -1, folder, mSmsType); 3236 sendEvent(evt); 3237 } 3238 mStorageUnlocked = true; 3239 /* After unlock this BroadcastReceiver is never needed */ 3240 unregister(); 3241 } else { 3242 Log.d(TAG, "onReceive: Unknown action " + action); 3243 } 3244 } 3245 } 3246 3247 /** 3248 * Handle MMS sent intents in disconnected(MNS) state, where we do not need to send any 3249 * notifications. 3250 * @param context The context to use for provider operations 3251 * @param intent The intent received 3252 * @param result The result 3253 */ 3254 public static void actionMmsSent(Context context, Intent intent, int result, 3255 Map<Long, Msg> mmsMsgList) { 3256 /* 3257 * if transparent: 3258 * delete message and send notification(regardless of result) 3259 * else 3260 * Result == Success: 3261 * move to sent folder (will trigger notification) 3262 * Result == Fail: 3263 * move to outbox (send delivery fail notification) 3264 */ 3265 if(D) Log.d(TAG,"actionMmsSent()"); 3266 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3267 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3268 if(handle < 0) { 3269 Log.w(TAG, "Intent received for an invalid handle"); 3270 return; 3271 } 3272 ContentResolver resolver = context.getContentResolver(); 3273 if(transparent == 1) { 3274 /* The specification is a bit unclear about the transparent flag. If it is set 3275 * no copy of the message shall be kept in the send folder after the message 3276 * was send, but in the case of a send error, it is unclear what to do. 3277 * As it will not be transparent if we keep the message in any folder, 3278 * we delete the message regardless of the result. 3279 * If we however do have a MNS connection we need to send a notification. */ 3280 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle); 3281 /* Delete from observer message list to avoid delete notifications */ 3282 if(mmsMsgList != null) { 3283 synchronized(mmsMsgList) { 3284 mmsMsgList.remove(handle); 3285 } 3286 } 3287 /* Delete message */ 3288 if(D) Log.d(TAG,"Transparent in use - delete"); 3289 resolver.delete(uri, null, null); 3290 } else if (result == Activity.RESULT_OK) { 3291 /* This will trigger a notification */ 3292 moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_SENT); 3293 } else { 3294 if(mmsMsgList != null) { 3295 synchronized(mmsMsgList) { 3296 Msg msg = mmsMsgList.get(handle); 3297 if(msg != null) { 3298 msg.type=Mms.MESSAGE_BOX_OUTBOX; 3299 } 3300 } 3301 } 3302 /* Hand further retries over to the MMS application */ 3303 moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_OUTBOX); 3304 } 3305 } 3306 3307 public static void actionMessageSentDisconnected(Context context, Intent intent, int result) { 3308 TYPE type = TYPE.fromOrdinal( 3309 intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal())); 3310 if(type == TYPE.MMS) { 3311 actionMmsSent(context, intent, result, null); 3312 } else { 3313 actionSmsSentDisconnected(context, intent, result); 3314 } 3315 } 3316 3317 public static void actionSmsSentDisconnected(Context context, Intent intent, int result) { 3318 /* Check permission for message deletion. */ 3319 if ((Binder.getCallingPid() != Process.myPid()) || 3320 (context.checkCallingOrSelfPermission("android.Manifest.permission.WRITE_SMS") 3321 != PackageManager.PERMISSION_GRANTED)) { 3322 Log.w(TAG, "actionSmsSentDisconnected: Not allowed to delete SMS/MMS messages"); 3323 return; 3324 } 3325 3326 boolean delete = false; 3327 //int retry = intent.getIntExtra(EXTRA_MESSAGE_SENT_RETRY, 0); 3328 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3329 String uriString = intent.getStringExtra(EXTRA_MESSAGE_SENT_URI); 3330 if(uriString == null) { 3331 // Nothing we can do about it, just bail out 3332 return; 3333 } 3334 Uri uri = Uri.parse(uriString); 3335 3336 if (result == Activity.RESULT_OK) { 3337 Log.d(TAG, "actionMessageSentDisconnected: result OK"); 3338 if (transparent == 0) { 3339 if (!Sms.moveMessageToFolder(context, uri, 3340 Sms.MESSAGE_TYPE_SENT, 0)) { 3341 Log.d(TAG, "Failed to move " + uri + " to SENT"); 3342 } 3343 } else { 3344 delete = true; 3345 } 3346 } else { 3347 /*if (retry == 1) { 3348 The retry feature only works while connected, else we fail the send, 3349 * and move the message to failed, to let the user/app resend manually later. 3350 } else */{ 3351 if (transparent == 0) { 3352 if (!Sms.moveMessageToFolder(context, uri, 3353 Sms.MESSAGE_TYPE_FAILED, 0)) { 3354 Log.d(TAG, "Failed to move " + uri + " to FAILED"); 3355 } 3356 } else { 3357 delete = true; 3358 } 3359 } 3360 } 3361 3362 if (delete) { 3363 /* Delete from DB */ 3364 ContentResolver resolver = context.getContentResolver(); 3365 if (resolver != null) { 3366 resolver.delete(uri, null, null); 3367 } else { 3368 Log.w(TAG, "Unable to get resolver"); 3369 } 3370 } 3371 } 3372 3373 private void registerPhoneServiceStateListener() { 3374 TelephonyManager tm = (TelephonyManager)mContext.getSystemService( 3375 Context.TELEPHONY_SERVICE); 3376 tm.listen(mPhoneListener, PhoneStateListener.LISTEN_SERVICE_STATE); 3377 } 3378 3379 private void unRegisterPhoneServiceStateListener() { 3380 TelephonyManager tm = (TelephonyManager)mContext.getSystemService( 3381 Context.TELEPHONY_SERVICE); 3382 tm.listen(mPhoneListener, PhoneStateListener.LISTEN_NONE); 3383 } 3384 3385 private void resendPendingMessages() { 3386 /* Send pending messages in outbox */ 3387 String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX; 3388 UserManager manager = UserManager.get(mContext); 3389 if (manager == null || !manager.isUserUnlocked()) return; 3390 Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, 3391 null); 3392 try { 3393 if (c != null && c.moveToFirst()) { 3394 do { 3395 long id = c.getLong(c.getColumnIndex(Sms._ID)); 3396 String msgBody = c.getString(c.getColumnIndex(Sms.BODY)); 3397 PushMsgInfo msgInfo = mPushMsgList.get(id); 3398 if (msgInfo == null || !msgInfo.resend || 3399 msgInfo.sendInProgress) { 3400 continue; 3401 } 3402 msgInfo.sendInProgress = true; 3403 sendMessage(msgInfo, msgBody); 3404 } while (c.moveToNext()); 3405 } 3406 } finally { 3407 if (c != null) c.close(); 3408 } 3409 3410 3411 } 3412 3413 private void failPendingMessages() { 3414 /* Move pending messages from outbox to failed */ 3415 String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX; 3416 Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, 3417 null); 3418 try { 3419 if (c != null && c.moveToFirst()) { 3420 do { 3421 long id = c.getLong(c.getColumnIndex(Sms._ID)); 3422 String msgBody = c.getString(c.getColumnIndex(Sms.BODY)); 3423 PushMsgInfo msgInfo = mPushMsgList.get(id); 3424 if (msgInfo == null || !msgInfo.resend) { 3425 continue; 3426 } 3427 Sms.moveMessageToFolder(mContext, msgInfo.uri, 3428 Sms.MESSAGE_TYPE_FAILED, 0); 3429 } while (c.moveToNext()); 3430 } 3431 } finally { 3432 if (c != null) c.close(); 3433 } 3434 3435 } 3436 3437 private void removeDeletedMessages() { 3438 /* Remove messages from virtual "deleted" folder (thread_id -1) */ 3439 mResolver.delete(Sms.CONTENT_URI, 3440 "thread_id = " + DELETED_THREAD_ID, null); 3441 } 3442 3443 private PhoneStateListener mPhoneListener = new PhoneStateListener() { 3444 @Override 3445 public void onServiceStateChanged(ServiceState serviceState) { 3446 Log.d(TAG, "Phone service state change: " + serviceState.getState()); 3447 if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) { 3448 resendPendingMessages(); 3449 } 3450 } 3451 }; 3452 3453 public void init() { 3454 if (mSmsBroadcastReceiver != null) { 3455 mSmsBroadcastReceiver.register(); 3456 } 3457 3458 if (mCeBroadcastReceiver != null) { 3459 mCeBroadcastReceiver.register(); 3460 } 3461 3462 registerPhoneServiceStateListener(); 3463 mInitialized = true; 3464 } 3465 3466 public void deinit() { 3467 mInitialized = false; 3468 unregisterObserver(); 3469 if (mSmsBroadcastReceiver != null) { 3470 mSmsBroadcastReceiver.unregister(); 3471 } 3472 unRegisterPhoneServiceStateListener(); 3473 failPendingMessages(); 3474 removeDeletedMessages(); 3475 } 3476 3477 public boolean handleSmsSendIntent(Context context, Intent intent){ 3478 TYPE type = TYPE.fromOrdinal( 3479 intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal())); 3480 if(type == TYPE.MMS) { 3481 return handleMmsSendIntent(context, intent); 3482 } else { 3483 if(mInitialized) { 3484 mSmsBroadcastReceiver.onReceive(context, intent); 3485 return true; 3486 } 3487 } 3488 return false; 3489 } 3490 3491 public boolean handleMmsSendIntent(Context context, Intent intent){ 3492 if(D) Log.w(TAG, "handleMmsSendIntent()"); 3493 if(!mMnsClient.isConnected()) { 3494 // No need to handle notifications, just use default handling 3495 if(D) Log.w(TAG, "MNS not connected - use static handling"); 3496 return false; 3497 } 3498 long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1); 3499 int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED); 3500 actionMmsSent(context, intent, result, getMsgListMms()); 3501 if(handle < 0) { 3502 Log.w(TAG, "Intent received for an invalid handle"); 3503 return true; 3504 } 3505 if(result != Activity.RESULT_OK) { 3506 if(mObserverRegistered) { 3507 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, handle, 3508 getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS); 3509 sendEvent(evt); 3510 } 3511 } else { 3512 int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0); 3513 if(transparent != 0) { 3514 if(mObserverRegistered) { 3515 Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, handle, 3516 getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS); 3517 sendEvent(evt); 3518 } 3519 } 3520 } 3521 return true; 3522 } 3523 3524} 3525