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