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