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