1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.mapclient; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothMapClient; 21 import android.content.ContentResolver; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.database.ContentObserver; 25 import android.database.Cursor; 26 import android.net.Uri; 27 import android.provider.Telephony; 28 import android.provider.Telephony.Mms; 29 import android.provider.Telephony.MmsSms; 30 import android.provider.Telephony.Sms; 31 import android.provider.Telephony.Threads; 32 import android.telephony.PhoneNumberUtils; 33 import android.telephony.SubscriptionInfo; 34 import android.telephony.SubscriptionManager; 35 import android.telephony.TelephonyManager; 36 import android.util.ArraySet; 37 import android.util.Log; 38 39 import com.android.bluetooth.Utils; 40 import com.android.bluetooth.map.BluetoothMapbMessageMime; 41 import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart; 42 import com.android.vcard.VCardConstants; 43 import com.android.vcard.VCardEntry; 44 import com.android.vcard.VCardProperty; 45 46 import com.google.android.mms.pdu.PduHeaders; 47 48 import java.util.Arrays; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Set; 52 53 class MapClientContent { 54 55 private static final String INBOX_PATH = "telecom/msg/inbox"; 56 private static final String TAG = "MapClientContent"; 57 private static final int DEFAULT_CHARSET = 106; 58 private static final int ORIGINATOR_ADDRESS_TYPE = 137; 59 private static final int RECIPIENT_ADDRESS_TYPE = 151; 60 61 final BluetoothDevice mDevice; 62 private final Context mContext; 63 private final Callbacks mCallbacks; 64 private final ContentResolver mResolver; 65 ContentObserver mContentObserver; 66 String mPhoneNumber = null; 67 private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 68 private SubscriptionManager mSubscriptionManager; 69 private TelephonyManager mTelephonyManager; 70 private HashMap<String, Uri> mHandleToUriMap = new HashMap<>(); 71 private HashMap<Uri, MessageStatus> mUriToHandleMap = new HashMap<>(); 72 73 /** 74 * Callbacks 75 * API to notify about statusChanges as observed from the content provider 76 */ 77 interface Callbacks { onMessageStatusChanged(String handle, int status)78 void onMessageStatusChanged(String handle, int status); 79 } 80 81 /** 82 * MapClientContent manages all interactions between Bluetooth and the messaging provider. 83 * 84 * Changes to the database are mirrored between the remote and local providers, specifically new 85 * messages, changes to read status, and removal of messages. 86 * 87 * Object is invalid after cleanUp() is called. 88 * 89 * context: the context that all content provider interactions are conducted 90 * MceStateMachine: the interface to send outbound updates such as when a message is read 91 * locally 92 * device: the associated Bluetooth device used for associating messages with a subscription 93 */ MapClientContent(Context context, Callbacks callbacks, BluetoothDevice device)94 MapClientContent(Context context, Callbacks callbacks, 95 BluetoothDevice device) { 96 mContext = context; 97 mDevice = device; 98 mCallbacks = callbacks; 99 mResolver = mContext.getContentResolver(); 100 101 mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); 102 mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 103 mSubscriptionManager 104 .addSubscriptionInfoRecord(mDevice.getAddress(), Utils.getName(mDevice), 0, 105 SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); 106 SubscriptionInfo info = mSubscriptionManager 107 .getActiveSubscriptionInfoForIcc(mDevice.getAddress()); 108 if (info != null) { 109 mSubscriptionId = info.getSubscriptionId(); 110 } 111 112 mContentObserver = new ContentObserver(null) { 113 @Override 114 public boolean deliverSelfNotifications() { 115 return false; 116 } 117 118 @Override 119 public void onChange(boolean selfChange) { 120 logV("onChange(self=" + selfChange + ")"); 121 findChangeInDatabase(); 122 } 123 124 @Override 125 public void onChange(boolean selfChange, Uri uri) { 126 logV("onChange(self=" + selfChange + ", uri=" + uri.toString() + ")"); 127 findChangeInDatabase(); 128 } 129 }; 130 131 clearMessages(mContext, mSubscriptionId); 132 mResolver.registerContentObserver(Sms.CONTENT_URI, true, mContentObserver); 133 mResolver.registerContentObserver(Mms.CONTENT_URI, true, mContentObserver); 134 mResolver.registerContentObserver(MmsSms.CONTENT_URI, true, mContentObserver); 135 } 136 clearAllContent(Context context)137 static void clearAllContent(Context context) { 138 SubscriptionManager subscriptionManager = 139 context.getSystemService(SubscriptionManager.class); 140 List<SubscriptionInfo> subscriptions = subscriptionManager.getActiveSubscriptionInfoList(); 141 if (subscriptions == null) { 142 Log.w(TAG, "Active subscription list is missing"); 143 return; 144 } 145 for (SubscriptionInfo info : subscriptions) { 146 if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) { 147 clearMessages(context, info.getSubscriptionId()); 148 try { 149 subscriptionManager.removeSubscriptionInfoRecord(info.getIccId(), 150 SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); 151 } catch (Exception e) { 152 Log.w(TAG, "cleanUp failed: " + e.toString()); 153 } 154 } 155 } 156 } 157 logI(String message)158 private static void logI(String message) { 159 Log.i(TAG, message); 160 } 161 logD(String message)162 private static void logD(String message) { 163 if (MapClientService.DBG) { 164 Log.d(TAG, message); 165 } 166 } 167 logV(String message)168 private static void logV(String message) { 169 if (MapClientService.VDBG) { 170 Log.v(TAG, message); 171 } 172 } 173 174 /** 175 * This number is necessary for thread_id to work properly. thread_id is needed for 176 * (group) MMS messages to be displayed/stitched correctly. 177 */ setRemoteDeviceOwnNumber(String phoneNumber)178 void setRemoteDeviceOwnNumber(String phoneNumber) { 179 mPhoneNumber = phoneNumber; 180 logV("Remote device " + mDevice.getAddress() + " phone number set to: " + mPhoneNumber); 181 } 182 183 /** 184 * storeMessage 185 * 186 * Store a message in database with the associated handle and timestamp. 187 * The handle is used to associate the local message with the remote message. 188 */ storeMessage(Bmessage message, String handle, Long timestamp, boolean seen)189 void storeMessage(Bmessage message, String handle, Long timestamp, boolean seen) { 190 logI("storeMessage(device=" + Utils.getLoggableAddress(mDevice) + ", time=" + timestamp 191 + ", handle=" + handle + ", type=" + message.getType() 192 + ", folder=" + message.getFolder()); 193 194 switch (message.getType()) { 195 case MMS: 196 storeMms(message, handle, timestamp, seen); 197 return; 198 case SMS_CDMA: 199 case SMS_GSM: 200 storeSms(message, handle, timestamp, seen); 201 return; 202 default: 203 logD("Request to store unsupported message type: " + message.getType()); 204 } 205 } 206 storeSms(Bmessage message, String handle, Long timestamp, boolean seen)207 private void storeSms(Bmessage message, String handle, Long timestamp, boolean seen) { 208 logD("storeSms"); 209 logV(message.toString()); 210 VCardEntry originator = message.getOriginator(); 211 String recipients; 212 if (INBOX_PATH.equals(message.getFolder())) { 213 recipients = getOriginatorNumber(message); 214 } else { 215 recipients = getFirstRecipientNumber(message); 216 if (recipients == null) { 217 logD("invalid recipients"); 218 return; 219 } 220 } 221 logV("Received SMS from Number " + recipients); 222 String messageContent; 223 224 Uri contentUri = INBOX_PATH.equalsIgnoreCase(message.getFolder()) ? Sms.Inbox.CONTENT_URI 225 : Sms.Sent.CONTENT_URI; 226 ContentValues values = new ContentValues(); 227 long threadId = getThreadId(message); 228 int readStatus = message.getStatus() == Bmessage.Status.READ ? 1 : 0; 229 230 values.put(Sms.THREAD_ID, threadId); 231 values.put(Sms.ADDRESS, recipients); 232 values.put(Sms.BODY, message.getBodyContent()); 233 values.put(Sms.SUBSCRIPTION_ID, mSubscriptionId); 234 values.put(Sms.DATE, timestamp); 235 values.put(Sms.READ, readStatus); 236 values.put(Sms.SEEN, seen); 237 238 Uri results = mResolver.insert(contentUri, values); 239 mHandleToUriMap.put(handle, results); 240 mUriToHandleMap.put(results, new MessageStatus(handle, readStatus)); 241 logD("Map InsertedThread" + results); 242 } 243 244 /** 245 * deleteMessage 246 * remove a message from the local provider based on a remote change 247 */ deleteMessage(String handle)248 void deleteMessage(String handle) { 249 logD("deleting handle" + handle); 250 Uri messageToChange = mHandleToUriMap.get(handle); 251 if (messageToChange != null) { 252 mResolver.delete(messageToChange, null); 253 } 254 } 255 256 257 /** 258 * markRead 259 * mark a message read in the local provider based on a remote change 260 */ markRead(String handle)261 void markRead(String handle) { 262 logD("marking read " + handle); 263 Uri messageToChange = mHandleToUriMap.get(handle); 264 if (messageToChange != null) { 265 ContentValues values = new ContentValues(); 266 values.put(Sms.READ, 1); 267 mResolver.update(messageToChange, values, null); 268 } 269 } 270 271 /** 272 * findChangeInDatabase 273 * compare the current state of the local content provider to the expected state and propagate 274 * changes to the remote. 275 */ findChangeInDatabase()276 private void findChangeInDatabase() { 277 HashMap<Uri, MessageStatus> originalUriToHandleMap; 278 HashMap<Uri, MessageStatus> duplicateUriToHandleMap; 279 280 originalUriToHandleMap = mUriToHandleMap; 281 duplicateUriToHandleMap = new HashMap<>(originalUriToHandleMap); 282 for (Uri uri : new Uri[]{Mms.CONTENT_URI, Sms.CONTENT_URI}) { 283 Cursor cursor = mResolver.query(uri, null, null, null, null); 284 while (cursor.moveToNext()) { 285 Uri index = Uri 286 .withAppendedPath(uri, cursor.getString(cursor.getColumnIndex("_id"))); 287 int readStatus = cursor.getInt(cursor.getColumnIndex(Sms.READ)); 288 MessageStatus currentMessage = duplicateUriToHandleMap.remove(index); 289 if (currentMessage != null && currentMessage.mRead != readStatus) { 290 logV(currentMessage.mHandle); 291 currentMessage.mRead = readStatus; 292 mCallbacks.onMessageStatusChanged(currentMessage.mHandle, 293 BluetoothMapClient.READ); 294 } 295 } 296 } 297 for (HashMap.Entry record : duplicateUriToHandleMap.entrySet()) { 298 logV("Deleted " + ((MessageStatus) record.getValue()).mHandle); 299 originalUriToHandleMap.remove(record.getKey()); 300 mCallbacks.onMessageStatusChanged(((MessageStatus) record.getValue()).mHandle, 301 BluetoothMapClient.DELETED); 302 } 303 } 304 storeMms(Bmessage message, String handle, Long timestamp, boolean seen)305 private void storeMms(Bmessage message, String handle, Long timestamp, boolean seen) { 306 logD("storeMms"); 307 logV(message.toString()); 308 try { 309 ContentValues values = new ContentValues(); 310 long threadId = getThreadId(message); 311 BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime(); 312 mmsBmessage.parseMsgPart(message.getBodyContent()); 313 int read = message.getStatus() == Bmessage.Status.READ ? 1 : 0; 314 Uri contentUri; 315 int messageBox; 316 if (INBOX_PATH.equalsIgnoreCase(message.getFolder())) { 317 contentUri = Mms.Inbox.CONTENT_URI; 318 messageBox = Mms.MESSAGE_BOX_INBOX; 319 } else { 320 contentUri = Mms.Sent.CONTENT_URI; 321 messageBox = Mms.MESSAGE_BOX_SENT; 322 } 323 logD("Parsed"); 324 values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId); 325 values.put(Mms.THREAD_ID, threadId); 326 values.put(Mms.DATE, timestamp / 1000L); 327 values.put(Mms.TEXT_ONLY, true); 328 values.put(Mms.MESSAGE_BOX, messageBox); 329 values.put(Mms.READ, read); 330 values.put(Mms.SEEN, seen); 331 values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ); 332 values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION); 333 values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL); 334 values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO); 335 values.put(Mms.TRANSACTION_ID, "T" + Long.toHexString(System.currentTimeMillis())); 336 values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO); 337 values.put(Mms.LOCKED, 0); 338 values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related"); 339 values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR); 340 values.put(Mms.MESSAGE_SIZE, mmsBmessage.getSize()); 341 342 Uri results = mResolver.insert(contentUri, values); 343 mHandleToUriMap.put(handle, results); 344 mUriToHandleMap.put(results, new MessageStatus(handle, read)); 345 346 logD("Map InsertedThread" + results); 347 348 for (MimePart part : mmsBmessage.getMimeParts()) { 349 storeMmsPart(part, results); 350 } 351 352 storeAddressPart(message, results); 353 354 String messageContent = mmsBmessage.getMessageAsText(); 355 356 values.put(Mms.Part.CONTENT_TYPE, "plain/text"); 357 values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId); 358 } catch (Exception e) { 359 Log.e(TAG, e.toString()); 360 throw e; 361 } 362 } 363 storeMmsPart(MimePart messagePart, Uri messageUri)364 private Uri storeMmsPart(MimePart messagePart, Uri messageUri) { 365 ContentValues values = new ContentValues(); 366 values.put(Mms.Part.CONTENT_TYPE, "text/plain"); 367 values.put(Mms.Part.CHARSET, DEFAULT_CHARSET); 368 values.put(Mms.Part.FILENAME, "text_1.txt"); 369 values.put(Mms.Part.NAME, "text_1.txt"); 370 values.put(Mms.Part.CONTENT_ID, messagePart.mContentId); 371 values.put(Mms.Part.CONTENT_LOCATION, messagePart.mContentLocation); 372 values.put(Mms.Part.TEXT, messagePart.getDataAsString()); 373 374 Uri contentUri = Uri.parse(messageUri.toString() + "/part"); 375 Uri results = mResolver.insert(contentUri, values); 376 logD("Inserted" + results); 377 return results; 378 } 379 storeAddressPart(Bmessage message, Uri messageUri)380 private void storeAddressPart(Bmessage message, Uri messageUri) { 381 ContentValues values = new ContentValues(); 382 Uri contentUri = Uri.parse(messageUri.toString() + "/addr"); 383 String originator = getOriginatorNumber(message); 384 values.put(Mms.Addr.CHARSET, DEFAULT_CHARSET); 385 386 values.put(Mms.Addr.ADDRESS, originator); 387 values.put(Mms.Addr.TYPE, ORIGINATOR_ADDRESS_TYPE); 388 mResolver.insert(contentUri, values); 389 390 Set<String> messageContacts = new ArraySet<>(); 391 getRecipientsFromMessage(message, messageContacts); 392 for (String recipient : messageContacts) { 393 values.put(Mms.Addr.ADDRESS, recipient); 394 values.put(Mms.Addr.TYPE, RECIPIENT_ADDRESS_TYPE); 395 mResolver.insert(contentUri, values); 396 } 397 } 398 insertIntoMmsTable(String subject)399 private Uri insertIntoMmsTable(String subject) { 400 ContentValues mmsValues = new ContentValues(); 401 mmsValues.put(Mms.TEXT_ONLY, 1); 402 mmsValues.put(Mms.MESSAGE_TYPE, 128); 403 mmsValues.put(Mms.SUBJECT, subject); 404 return mResolver.insert(Mms.CONTENT_URI, mmsValues); 405 } 406 407 /** 408 * cleanUp 409 * clear the subscription info and content on shutdown 410 */ cleanUp()411 void cleanUp() { 412 logD("cleanUp(device=" + Utils.getLoggableAddress(mDevice) 413 + "subscriptionId=" + mSubscriptionId); 414 mResolver.unregisterContentObserver(mContentObserver); 415 clearMessages(mContext, mSubscriptionId); 416 try { 417 mSubscriptionManager.removeSubscriptionInfoRecord(mDevice.getAddress(), 418 SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM); 419 mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 420 } catch (Exception e) { 421 Log.w(TAG, "cleanUp failed: " + e.toString()); 422 } 423 } 424 425 /** 426 * clearMessages 427 * clean up the content provider on startup 428 */ clearMessages(Context context, int subscriptionId)429 private static void clearMessages(Context context, int subscriptionId) { 430 logD("clearMessages(subscriptionId=" + subscriptionId); 431 432 ContentResolver resolver = context.getContentResolver(); 433 String threads = new String(); 434 435 Uri uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); 436 Cursor threadCursor = resolver.query(uri, null, null, null, null); 437 while (threadCursor.moveToNext()) { 438 threads += threadCursor.getInt(threadCursor.getColumnIndex(Threads._ID)) + ", "; 439 } 440 441 resolver.delete(Sms.CONTENT_URI, Sms.SUBSCRIPTION_ID + " =? ", 442 new String[]{Integer.toString(subscriptionId)}); 443 resolver.delete(Mms.CONTENT_URI, Mms.SUBSCRIPTION_ID + " =? ", 444 new String[]{Integer.toString(subscriptionId)}); 445 if (threads.length() > 2) { 446 threads = threads.substring(0, threads.length() - 2); 447 resolver.delete(Threads.CONTENT_URI, Threads._ID + " IN (" + threads + ")", null); 448 } 449 } 450 451 /** 452 * getThreadId 453 * utilize the originator and recipients to obtain the thread id 454 */ getThreadId(Bmessage message)455 private long getThreadId(Bmessage message) { 456 457 Set<String> messageContacts = new ArraySet<>(); 458 String originator = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message)); 459 if (originator != null) { 460 messageContacts.add(originator); 461 } 462 getRecipientsFromMessage(message, messageContacts); 463 // If there is only one contact don't remove it. 464 if (messageContacts.isEmpty()) { 465 return Telephony.Threads.COMMON_THREAD; 466 } else if (messageContacts.size() > 1) { 467 if (mPhoneNumber == null) { 468 Log.w(TAG, "getThreadId called, mPhoneNumber never found."); 469 } 470 messageContacts.removeIf(number -> (PhoneNumberUtils.areSamePhoneNumber(number, 471 mPhoneNumber, mTelephonyManager.getNetworkCountryIso()))); 472 } 473 474 logV("Contacts = " + messageContacts.toString()); 475 return Telephony.Threads.getOrCreateThreadId(mContext, messageContacts); 476 } 477 getRecipientsFromMessage(Bmessage message, Set<String> messageContacts)478 private void getRecipientsFromMessage(Bmessage message, Set<String> messageContacts) { 479 List<VCardEntry> recipients = message.getRecipients(); 480 for (VCardEntry recipient : recipients) { 481 List<VCardEntry.PhoneData> phoneData = recipient.getPhoneList(); 482 if (phoneData != null && !phoneData.isEmpty()) { 483 messageContacts 484 .add(PhoneNumberUtils.extractNetworkPortion(phoneData.get(0).getNumber())); 485 } 486 } 487 } 488 getOriginatorNumber(Bmessage message)489 private String getOriginatorNumber(Bmessage message) { 490 VCardEntry originator = message.getOriginator(); 491 if (originator == null) { 492 return null; 493 } 494 495 List<VCardEntry.PhoneData> phoneData = originator.getPhoneList(); 496 if (phoneData == null || phoneData.isEmpty()) { 497 return null; 498 } 499 500 return PhoneNumberUtils.extractNetworkPortion(phoneData.get(0).getNumber()); 501 } 502 getFirstRecipientNumber(Bmessage message)503 private String getFirstRecipientNumber(Bmessage message) { 504 List<VCardEntry> recipients = message.getRecipients(); 505 if (recipients == null || recipients.isEmpty()) { 506 return null; 507 } 508 509 List<VCardEntry.PhoneData> phoneData = recipients.get(0).getPhoneList(); 510 if (phoneData == null || phoneData.isEmpty()) { 511 return null; 512 } 513 514 return phoneData.get(0).getNumber(); 515 } 516 517 /** 518 * addThreadContactToEntries 519 * utilizing the thread id fill in the appropriate fields of bmsg with the intended recipients 520 */ addThreadContactsToEntries(Bmessage bmsg, String thread)521 boolean addThreadContactsToEntries(Bmessage bmsg, String thread) { 522 String threadId = Uri.parse(thread).getLastPathSegment(); 523 524 logD("MATCHING THREAD" + threadId); 525 logD(MmsSms.CONTENT_CONVERSATIONS_URI + threadId + "/recipients"); 526 527 Cursor cursor = mResolver 528 .query(Uri.withAppendedPath(MmsSms.CONTENT_CONVERSATIONS_URI, 529 threadId + "/recipients"), 530 null, null, 531 null, null); 532 533 if (cursor.moveToNext()) { 534 logD("Columns" + Arrays.toString(cursor.getColumnNames())); 535 logV("CONTACT LIST: " + cursor.getString(cursor.getColumnIndex("recipient_ids"))); 536 addRecipientsToEntries(bmsg, 537 cursor.getString(cursor.getColumnIndex("recipient_ids")).split(" ")); 538 return true; 539 } else { 540 Log.w(TAG, "Thread Not Found"); 541 return false; 542 } 543 } 544 545 addRecipientsToEntries(Bmessage bmsg, String[] recipients)546 private void addRecipientsToEntries(Bmessage bmsg, String[] recipients) { 547 logV("CONTACT LIST: " + Arrays.toString(recipients)); 548 for (String recipient : recipients) { 549 Cursor cursor = mResolver 550 .query(Uri.parse("content://mms-sms/canonical-address/" + recipient), null, 551 null, null, 552 null); 553 while (cursor.moveToNext()) { 554 String number = cursor.getString(cursor.getColumnIndex(Mms.Addr.ADDRESS)); 555 logV("CONTACT number: " + number); 556 VCardEntry destEntry = new VCardEntry(); 557 VCardProperty destEntryPhone = new VCardProperty(); 558 destEntryPhone.setName(VCardConstants.PROPERTY_TEL); 559 destEntryPhone.addValues(number); 560 destEntry.addProperty(destEntryPhone); 561 bmsg.addRecipient(destEntry); 562 } 563 } 564 } 565 566 /** 567 * Get the total number of messages we've stored under this device's subscription ID, for a 568 * given message source, provided by the "uri" parameter. 569 */ getStoredMessagesCount(Uri uri)570 private int getStoredMessagesCount(Uri uri) { 571 if (mSubscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 572 logV("getStoredMessagesCount(uri=" + uri + "): Failed, no subscription ID"); 573 return 0; 574 } 575 576 Cursor cursor = null; 577 if (Sms.CONTENT_URI.equals(uri) || Sms.Inbox.CONTENT_URI.equals(uri) 578 || Sms.Sent.CONTENT_URI.equals(uri)) { 579 cursor = mResolver.query(uri, new String[] {"count(*)"}, Sms.SUBSCRIPTION_ID + " =? ", 580 new String[]{Integer.toString(mSubscriptionId)}, null); 581 } else if (Mms.CONTENT_URI.equals(uri) || Mms.Inbox.CONTENT_URI.equals(uri) 582 || Mms.Sent.CONTENT_URI.equals(uri)) { 583 cursor = mResolver.query(uri, new String[] {"count(*)"}, Mms.SUBSCRIPTION_ID + " =? ", 584 new String[]{Integer.toString(mSubscriptionId)}, null); 585 } else if (Threads.CONTENT_URI.equals(uri)) { 586 uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); 587 cursor = mResolver.query(uri, new String[] {"count(*)"}, null, null, null); 588 } 589 590 if (cursor == null) { 591 return 0; 592 } 593 594 cursor.moveToFirst(); 595 int count = cursor.getInt(0); 596 cursor.close(); 597 598 return count; 599 } 600 dump(StringBuilder sb)601 public void dump(StringBuilder sb) { 602 sb.append(" Device Message DB:"); 603 sb.append("\n Subscription ID: " + mSubscriptionId); 604 if (mSubscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 605 sb.append("\n SMS Messages (Inbox/Sent/Total): " 606 + getStoredMessagesCount(Sms.Inbox.CONTENT_URI) 607 + " / " + getStoredMessagesCount(Sms.Sent.CONTENT_URI) 608 + " / " + getStoredMessagesCount(Sms.CONTENT_URI)); 609 610 sb.append("\n MMS Messages (Inbox/Sent/Total): " 611 + getStoredMessagesCount(Mms.Inbox.CONTENT_URI) 612 + " / " + getStoredMessagesCount(Mms.Sent.CONTENT_URI) 613 + " / " + getStoredMessagesCount(Mms.CONTENT_URI)); 614 615 sb.append("\n Threads: " + getStoredMessagesCount(Threads.CONTENT_URI)); 616 } 617 sb.append("\n"); 618 } 619 620 /** 621 * MessageStatus 622 * 623 * Helper class to store associations between remote and local provider based on message handle 624 * and read status 625 */ 626 class MessageStatus { 627 628 String mHandle; 629 int mRead; 630 MessageStatus(String handle, int read)631 MessageStatus(String handle, int read) { 632 mHandle = handle; 633 mRead = read; 634 } 635 636 @Override equals(Object other)637 public boolean equals(Object other) { 638 return ((other instanceof MessageStatus) && ((MessageStatus) other).mHandle 639 .equals(mHandle)); 640 } 641 } 642 } 643