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