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