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