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