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