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.bluetooth.BluetoothProfile; 18 import android.bluetooth.BluetoothProtoEnums; 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.database.Cursor; 22 import android.net.Uri; 23 import android.os.ParcelFileDescriptor; 24 import android.provider.BaseColumns; 25 import android.provider.ContactsContract; 26 import android.provider.ContactsContract.Contacts; 27 import android.provider.ContactsContract.PhoneLookup; 28 import android.provider.Telephony.CanonicalAddressesColumns; 29 import android.provider.Telephony.Mms; 30 import android.provider.Telephony.MmsSms; 31 import android.provider.Telephony.Sms; 32 import android.provider.Telephony.Threads; 33 import android.telephony.PhoneNumberUtils; 34 import android.telephony.TelephonyManager; 35 import android.text.TextUtils; 36 import android.text.util.Rfc822Token; 37 import android.text.util.Rfc822Tokenizer; 38 import android.util.Log; 39 40 import com.android.bluetooth.BluetoothMethodProxy; 41 import com.android.bluetooth.BluetoothStatsLog; 42 import com.android.bluetooth.DeviceWorkArounds; 43 import com.android.bluetooth.SignedLongLong; 44 import com.android.bluetooth.Utils; 45 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils; 46 import com.android.bluetooth.map.BluetoothMapUtils.TYPE; 47 import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart; 48 import com.android.bluetooth.mapapi.BluetoothMapContract; 49 import com.android.bluetooth.mapapi.BluetoothMapContract.ConversationColumns; 50 import com.android.internal.annotations.VisibleForTesting; 51 52 import com.google.android.mms.pdu.CharacterSets; 53 import com.google.android.mms.pdu.PduHeaders; 54 55 import java.io.ByteArrayOutputStream; 56 import java.io.Closeable; 57 import java.io.FileInputStream; 58 import java.io.FileNotFoundException; 59 import java.io.IOException; 60 import java.io.InputStream; 61 import java.io.UnsupportedEncodingException; 62 import java.nio.charset.StandardCharsets; 63 import java.util.Arrays; 64 import java.util.HashMap; 65 import java.util.List; 66 import java.util.Locale; 67 import java.util.Map; 68 import java.util.stream.Collectors; 69 70 // Next tag value for ContentProfileErrorReportUtils.report(): 15 71 public class BluetoothMapContent { 72 private static final String TAG = BluetoothMapContent.class.getSimpleName(); 73 74 // Parameter Mask for selection of parameters to return in listings 75 private static final int MASK_SUBJECT = 0x00000001; 76 @VisibleForTesting static final int MASK_DATETIME = 0x00000002; 77 @VisibleForTesting static final int MASK_SENDER_NAME = 0x00000004; 78 @VisibleForTesting static final int MASK_SENDER_ADDRESSING = 0x00000008; 79 private static final int MASK_RECIPIENT_NAME = 0x00000010; 80 @VisibleForTesting static final int MASK_RECIPIENT_ADDRESSING = 0x00000020; 81 private static final int MASK_TYPE = 0x00000040; 82 private static final int MASK_SIZE = 0x00000080; 83 private static final int MASK_RECEPTION_STATUS = 0x00000100; 84 private static final int MASK_TEXT = 0x00000200; 85 @VisibleForTesting static final int MASK_ATTACHMENT_SIZE = 0x00000400; 86 private static final int MASK_PRIORITY = 0x00000800; 87 private static final int MASK_READ = 0x00001000; 88 private static final int MASK_SENT = 0x00002000; 89 private static final int MASK_PROTECTED = 0x00004000; 90 // private static final int MASK_REPLYTO_ADDRESSING = 0x00008000; 91 // TODO: Duplicate in proposed spec 92 // private static final int MASK_RECEPTION_STATE = 0x00010000; 93 @VisibleForTesting static final int MASK_DELIVERY_STATUS = 0x00010000; 94 private static final int MASK_CONVERSATION_ID = 0x00020000; 95 private static final int MASK_CONVERSATION_NAME = 0x00040000; 96 // private static final int MASK_FOLDER_TYPE = 0x00100000; 97 // TODO: about to be removed from proposed spec 98 // private static final int MASK_SEQUENCE_NUMBER = 0x00200000; 99 private static final int MASK_ATTACHMENT_MIME = 0x00100000; 100 101 private static final int CONVO_PARAM_MASK_CONVO_NAME = 0x00000001; 102 private static final int CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY = 0x00000002; 103 private static final int CONVO_PARAM_MASK_CONVO_READ_STATUS = 0x00000004; 104 private static final int CONVO_PARAM_MASK_CONVO_VERSION_COUNTER = 0x00000008; 105 private static final int CONVO_PARAM_MASK_CONVO_SUMMARY = 0x00000010; 106 private static final int CONVO_PARAM_MASK_PARTICIPANTS = 0x00000020; 107 private static final int CONVO_PARAM_MASK_PART_UCI = 0x00000040; 108 private static final int CONVO_PARAM_MASK_PART_DISP_NAME = 0x00000080; 109 private static final int CONVO_PARAM_MASK_PART_CHAT_STATE = 0x00000100; 110 private static final int CONVO_PARAM_MASK_PART_LAST_ACTIVITY = 0x00000200; 111 private static final int CONVO_PARAM_MASK_PART_X_BT_UID = 0x00000400; 112 private static final int CONVO_PARAM_MASK_PART_NAME = 0x00000800; 113 private static final int CONVO_PARAM_MASK_PART_PRESENCE = 0x00001000; 114 private static final int CONVO_PARAM_MASK_PART_PRESENCE_TEXT = 0x00002000; 115 private static final int CONVO_PARAM_MASK_PART_PRIORITY = 0x00004000; 116 117 /* Default values for omitted or 0 parameterMask application parameters */ 118 // MAP specification states that the default value for parameter mask are 119 // the #REQUIRED attributes in the DTD, and not all enabled 120 public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL; 121 public static final long CONVO_PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL; 122 public static final long CONVO_PARAMETER_MASK_DEFAULT = 123 CONVO_PARAM_MASK_CONVO_NAME 124 | CONVO_PARAM_MASK_PARTICIPANTS 125 | CONVO_PARAM_MASK_PART_UCI 126 | CONVO_PARAM_MASK_PART_DISP_NAME; 127 128 private static final int FILTER_READ_STATUS_UNREAD_ONLY = 0x01; 129 private static final int FILTER_READ_STATUS_READ_ONLY = 0x02; 130 // private static final int FILTER_READ_STATUS_ALL = 0x00; 131 132 /* Type of MMS address. From Telephony.java it must be one of PduHeaders.BCC, */ 133 /* PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO. These are from PduHeaders.java */ 134 public static final int MMS_FROM = 0x89; 135 public static final int MMS_TO = 0x97; 136 public static final int MMS_BCC = 0x81; 137 public static final int MMS_CC = 0x82; 138 139 /* OMA-TS-MMS-ENC defined many types in X-Mms-Message-Type. 140 Only m-send-req (128) m-retrieve-conf (132), m-notification-ind (130) 141 are interested by user */ 142 private static final String INTERESTED_MESSAGE_TYPE_CLAUSE = 143 Utils.formatSimple( 144 "( %s = %d OR %s = %d OR %s = %d )", 145 Mms.MESSAGE_TYPE, 146 PduHeaders.MESSAGE_TYPE_SEND_REQ, 147 Mms.MESSAGE_TYPE, 148 PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF, 149 Mms.MESSAGE_TYPE, 150 PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); 151 152 private static final String INSERT_ADDRESS_TOKEN = "insert-address-token"; 153 154 private final Context mContext; 155 private final ContentResolver mResolver; 156 @VisibleForTesting final String mBaseUri; 157 private final BluetoothMapAccountItem mAccount; 158 /* The MasInstance reference is used to update persistent (over a connection) version counters*/ 159 private final BluetoothMapMasInstance mMasInstance; 160 @VisibleForTesting String mMessageVersion = BluetoothMapUtils.MAP_V10_STR; 161 162 private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK; 163 @VisibleForTesting int mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10; 164 165 static final String[] SMS_PROJECTION = 166 new String[] { 167 BaseColumns._ID, 168 Sms.THREAD_ID, 169 Sms.ADDRESS, 170 Sms.BODY, 171 Sms.DATE, 172 Sms.READ, 173 Sms.TYPE, 174 Sms.STATUS, 175 Sms.LOCKED, 176 Sms.ERROR_CODE 177 }; 178 179 static final String[] MMS_PROJECTION = 180 new String[] { 181 BaseColumns._ID, 182 Mms.THREAD_ID, 183 Mms.MESSAGE_ID, 184 Mms.MESSAGE_SIZE, 185 Mms.SUBJECT, 186 Mms.CONTENT_TYPE, 187 Mms.TEXT_ONLY, 188 Mms.DATE, 189 Mms.DATE_SENT, 190 Mms.READ, 191 Mms.MESSAGE_BOX, 192 Mms.STATUS, 193 Mms.PRIORITY, 194 }; 195 196 static final String[] SMS_CONVO_PROJECTION = 197 new String[] { 198 BaseColumns._ID, 199 Sms.THREAD_ID, 200 Sms.ADDRESS, 201 Sms.DATE, 202 Sms.READ, 203 Sms.TYPE, 204 Sms.STATUS, 205 Sms.LOCKED, 206 Sms.ERROR_CODE 207 }; 208 209 static final String[] MMS_CONVO_PROJECTION = 210 new String[] { 211 BaseColumns._ID, 212 Mms.THREAD_ID, 213 Mms.MESSAGE_ID, 214 Mms.MESSAGE_SIZE, 215 Mms.SUBJECT, 216 Mms.CONTENT_TYPE, 217 Mms.TEXT_ONLY, 218 Mms.DATE, 219 Mms.DATE_SENT, 220 Mms.READ, 221 Mms.MESSAGE_BOX, 222 Mms.STATUS, 223 Mms.PRIORITY, 224 Mms.Addr.ADDRESS 225 }; 226 227 /* CONVO LISTING projections and column indexes */ 228 @VisibleForTesting 229 static final String[] MMS_SMS_THREAD_PROJECTION = { 230 Threads._ID, 231 Threads.DATE, 232 Threads.SNIPPET, 233 Threads.SNIPPET_CHARSET, 234 Threads.READ, 235 Threads.RECIPIENT_IDS 236 }; 237 238 private static final String[] CONVO_VERSION_PROJECTION = 239 new String[] { 240 /* Thread information */ 241 ConversationColumns.THREAD_ID, 242 ConversationColumns.THREAD_NAME, 243 ConversationColumns.READ_STATUS, 244 ConversationColumns.LAST_THREAD_ACTIVITY, 245 ConversationColumns.SUMMARY, 246 }; 247 248 /* Optimize the Cursor access to avoid the need to do a getColumnIndex() */ 249 private static final int MMS_SMS_THREAD_COL_ID; 250 private static final int MMS_SMS_THREAD_COL_DATE; 251 private static final int MMS_SMS_THREAD_COL_SNIPPET; 252 private static final int MMS_SMS_THREAD_COL_SNIPPET_CS; 253 private static final int MMS_SMS_THREAD_COL_READ; 254 private static final int MMS_SMS_THREAD_COL_RECIPIENT_IDS; 255 256 static { 257 // TODO: This might not work, if the projection is mapped in the content provider... 258 // Change to init at first query? (Current use in the AOSP code is hard coded values 259 // unrelated to the projection used) 260 List<String> projection = Arrays.asList(MMS_SMS_THREAD_PROJECTION); 261 MMS_SMS_THREAD_COL_ID = projection.indexOf(Threads._ID); 262 MMS_SMS_THREAD_COL_DATE = projection.indexOf(Threads.DATE); 263 MMS_SMS_THREAD_COL_SNIPPET = projection.indexOf(Threads.SNIPPET); 264 MMS_SMS_THREAD_COL_SNIPPET_CS = projection.indexOf(Threads.SNIPPET_CHARSET); 265 MMS_SMS_THREAD_COL_READ = projection.indexOf(Threads.READ); 266 MMS_SMS_THREAD_COL_RECIPIENT_IDS = projection.indexOf(Threads.RECIPIENT_IDS); 267 } 268 269 @VisibleForTesting 270 static class FilterInfo { 271 public static final int TYPE_SMS = 0; 272 public static final int TYPE_MMS = 1; 273 public static final int TYPE_EMAIL = 2; 274 public static final int TYPE_IM = 3; 275 276 // TODO: Change to ENUM, to ensure correct usage 277 int mMsgType = TYPE_SMS; 278 int mPhoneType = 0; 279 String mPhoneNum = null; 280 String mPhoneAlphaTag = null; 281 /*column indices used to optimize queries */ 282 public int mMessageColId = -1; 283 public int mMessageColDate = -1; 284 public int mMessageColBody = -1; 285 public int mMessageColSubject = -1; 286 public int mMessageColFolder = -1; 287 public int mMessageColRead = -1; 288 public int mMessageColSize = -1; 289 public int mMessageColFromAddress = -1; 290 public int mMessageColToAddress = -1; 291 public int mMessageColCcAddress = -1; 292 public int mMessageColBccAddress = -1; 293 public int mMessageColReplyTo = -1; 294 public int mMessageColAccountId = -1; 295 public int mMessageColAttachment = -1; 296 public int mMessageColAttachmentSize = -1; 297 public int mMessageColAttachmentMime = -1; 298 public int mMessageColPriority = -1; 299 public int mMessageColProtected = -1; 300 public int mMessageColReception = -1; 301 public int mMessageColDelivery = -1; 302 public int mMessageColThreadId = -1; 303 public int mMessageColThreadName = -1; 304 305 public int mSmsColFolder = -1; 306 public int mSmsColRead = -1; 307 public int mSmsColId = -1; 308 public int mSmsColSubject = -1; 309 public int mSmsColAddress = -1; 310 public int mSmsColDate = -1; 311 public int mSmsColType = -1; 312 public int mSmsColThreadId = -1; 313 314 public int mMmsColRead = -1; 315 public int mMmsColFolder = -1; 316 public int mMmsColAttachmentSize = -1; 317 public int mMmsColTextOnly = -1; 318 public int mMmsColId = -1; 319 public int mMmsColSize = -1; 320 public int mMmsColDate = -1; 321 public int mMmsColSubject = -1; 322 public int mMmsColThreadId = -1; 323 324 public int mConvoColConvoId = -1; 325 public int mConvoColLastActivity = -1; 326 public int mConvoColName = -1; 327 public int mConvoColRead = -1; 328 public int mConvoColVersionCounter = -1; 329 public int mConvoColSummary = -1; 330 public int mContactColBtUid = -1; 331 public int mContactColChatState = -1; 332 public int mContactColContactUci = -1; 333 public int mContactColNickname = -1; 334 public int mContactColLastActive = -1; 335 public int mContactColName = -1; 336 public int mContactColPresenceState = -1; 337 public int mContactColPresenceText = -1; 338 public int mContactColPriority = -1; 339 setMessageColumns(Cursor c)340 public void setMessageColumns(Cursor c) { 341 mMessageColId = c.getColumnIndex(BluetoothMapContract.MessageColumns._ID); 342 mMessageColDate = c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE); 343 mMessageColSubject = c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT); 344 mMessageColFolder = c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID); 345 mMessageColRead = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ); 346 mMessageColSize = c.getColumnIndex(BluetoothMapContract.MessageColumns.MESSAGE_SIZE); 347 mMessageColFromAddress = 348 c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST); 349 mMessageColToAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST); 350 mMessageColAttachment = 351 c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT); 352 mMessageColAttachmentSize = 353 c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE); 354 mMessageColPriority = 355 c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY); 356 mMessageColProtected = 357 c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_PROTECTED); 358 mMessageColReception = 359 c.getColumnIndex(BluetoothMapContract.MessageColumns.RECEPTION_STATE); 360 mMessageColDelivery = 361 c.getColumnIndex(BluetoothMapContract.MessageColumns.DELIVERY_STATE); 362 mMessageColThreadId = c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID); 363 } 364 setEmailMessageColumns(Cursor c)365 public void setEmailMessageColumns(Cursor c) { 366 setMessageColumns(c); 367 mMessageColCcAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.CC_LIST); 368 mMessageColBccAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.BCC_LIST); 369 mMessageColReplyTo = 370 c.getColumnIndex(BluetoothMapContract.MessageColumns.REPLY_TO_LIST); 371 } 372 setImMessageColumns(Cursor c)373 public void setImMessageColumns(Cursor c) { 374 setMessageColumns(c); 375 mMessageColThreadName = 376 c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_NAME); 377 mMessageColAttachmentMime = 378 c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES); 379 // TODO this is temporary as text should come from parts table instead 380 mMessageColBody = c.getColumnIndex(BluetoothMapContract.MessageColumns.BODY); 381 } 382 setEmailImConvoColumns(Cursor c)383 public void setEmailImConvoColumns(Cursor c) { 384 mConvoColConvoId = c.getColumnIndex(BluetoothMapContract.ConversationColumns.THREAD_ID); 385 mConvoColLastActivity = 386 c.getColumnIndex(BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY); 387 mConvoColName = c.getColumnIndex(BluetoothMapContract.ConversationColumns.THREAD_NAME); 388 mConvoColRead = c.getColumnIndex(BluetoothMapContract.ConversationColumns.READ_STATUS); 389 mConvoColVersionCounter = 390 c.getColumnIndex(BluetoothMapContract.ConversationColumns.VERSION_COUNTER); 391 mConvoColSummary = c.getColumnIndex(BluetoothMapContract.ConversationColumns.SUMMARY); 392 setEmailImConvoContactColumns(c); 393 } 394 setEmailImConvoContactColumns(Cursor c)395 public void setEmailImConvoContactColumns(Cursor c) { 396 mContactColBtUid = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.X_BT_UID); 397 mContactColChatState = 398 c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.CHAT_STATE); 399 mContactColContactUci = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.UCI); 400 mContactColNickname = 401 c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NICKNAME); 402 mContactColLastActive = 403 c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE); 404 mContactColName = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME); 405 mContactColPresenceState = 406 c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE); 407 mContactColPresenceText = 408 c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.STATUS_TEXT); 409 mContactColPriority = 410 c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.PRIORITY); 411 } 412 setSmsColumns(Cursor c)413 public void setSmsColumns(Cursor c) { 414 mSmsColId = c.getColumnIndex(BaseColumns._ID); 415 mSmsColFolder = c.getColumnIndex(Sms.TYPE); 416 mSmsColRead = c.getColumnIndex(Sms.READ); 417 mSmsColSubject = c.getColumnIndex(Sms.BODY); 418 mSmsColAddress = c.getColumnIndex(Sms.ADDRESS); 419 mSmsColDate = c.getColumnIndex(Sms.DATE); 420 mSmsColType = c.getColumnIndex(Sms.TYPE); 421 mSmsColThreadId = c.getColumnIndex(Sms.THREAD_ID); 422 } 423 setMmsColumns(Cursor c)424 public void setMmsColumns(Cursor c) { 425 mMmsColId = c.getColumnIndex(BaseColumns._ID); 426 mMmsColFolder = c.getColumnIndex(Mms.MESSAGE_BOX); 427 mMmsColRead = c.getColumnIndex(Mms.READ); 428 mMmsColAttachmentSize = c.getColumnIndex(Mms.MESSAGE_SIZE); 429 mMmsColTextOnly = c.getColumnIndex(Mms.TEXT_ONLY); 430 mMmsColSize = c.getColumnIndex(Mms.MESSAGE_SIZE); 431 mMmsColDate = c.getColumnIndex(Mms.DATE); 432 mMmsColSubject = c.getColumnIndex(Mms.SUBJECT); 433 mMmsColThreadId = c.getColumnIndex(Mms.THREAD_ID); 434 } 435 } 436 BluetoothMapContent( final Context context, BluetoothMapAccountItem account, BluetoothMapMasInstance mas)437 public BluetoothMapContent( 438 final Context context, BluetoothMapAccountItem account, BluetoothMapMasInstance mas) { 439 mContext = context; 440 mResolver = mContext.getContentResolver(); 441 mMasInstance = mas; 442 if (mResolver == null) { 443 Log.d(TAG, "getContentResolver failed"); 444 } 445 446 if (account != null) { 447 mBaseUri = account.mBase_uri + "/"; 448 mAccount = account; 449 } else { 450 mBaseUri = null; 451 mAccount = null; 452 } 453 } 454 close(Closeable c)455 private static void close(Closeable c) { 456 try { 457 if (c != null) { 458 c.close(); 459 } 460 } catch (IOException e) { 461 ContentProfileErrorReportUtils.report( 462 BluetoothProfile.MAP, 463 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 464 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 465 0); 466 Log.w(TAG, e); 467 } 468 } 469 setProtected( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)470 private static void setProtected( 471 BluetoothMapMessageListingElement e, 472 Cursor c, 473 FilterInfo fi, 474 BluetoothMapAppParams ap) { 475 if ((ap.getParameterMask() & MASK_PROTECTED) != 0) { 476 String protect = "no"; 477 if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 478 int flagProtected = c.getInt(fi.mMessageColProtected); 479 if (flagProtected == 1) { 480 protect = "yes"; 481 } 482 } 483 Log.v(TAG, "setProtected: " + protect + "\n"); 484 e.setProtect(protect); 485 } 486 } 487 setThreadId( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)488 private static void setThreadId( 489 BluetoothMapMessageListingElement e, 490 Cursor c, 491 FilterInfo fi, 492 BluetoothMapAppParams ap) { 493 if ((ap.getParameterMask() & MASK_CONVERSATION_ID) != 0) { 494 long threadId = 0; 495 TYPE type = TYPE.SMS_GSM; // Just used for handle encoding 496 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 497 threadId = c.getLong(fi.mSmsColThreadId); 498 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 499 threadId = c.getLong(fi.mMmsColThreadId); 500 type = TYPE.MMS; // Just used for handle encoding 501 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 502 threadId = c.getLong(fi.mMessageColThreadId); 503 type = TYPE.EMAIL; // Just used for handle encoding 504 } 505 Log.v(TAG, "setThreadId: " + threadId + "\n"); 506 e.setThreadId(threadId, type); 507 } 508 } 509 setThreadName( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)510 private static void setThreadName( 511 BluetoothMapMessageListingElement e, 512 Cursor c, 513 FilterInfo fi, 514 BluetoothMapAppParams ap) { 515 // TODO: Maybe this should be valid for SMS/MMS 516 if ((ap.getParameterMask() & MASK_CONVERSATION_NAME) != 0) { 517 if (fi.mMsgType == FilterInfo.TYPE_IM) { 518 String threadName = c.getString(fi.mMessageColThreadName); 519 e.setThreadName(threadName); 520 Log.v(TAG, "setThreadName: " + threadName + "\n"); 521 } 522 } 523 } 524 setSent( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)525 private static void setSent( 526 BluetoothMapMessageListingElement e, 527 Cursor c, 528 FilterInfo fi, 529 BluetoothMapAppParams ap) { 530 if ((ap.getParameterMask() & MASK_SENT) != 0) { 531 int msgType = 0; 532 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 533 msgType = c.getInt(fi.mSmsColFolder); 534 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 535 msgType = c.getInt(fi.mMmsColFolder); 536 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 537 msgType = c.getInt(fi.mMessageColFolder); 538 } 539 String sent = null; 540 if (msgType == 2) { 541 sent = "yes"; 542 } else { 543 sent = "no"; 544 } 545 Log.v(TAG, "setSent: " + sent); 546 e.setSent(sent); 547 } 548 } 549 setRead( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)550 private static void setRead( 551 BluetoothMapMessageListingElement e, 552 Cursor c, 553 FilterInfo fi, 554 BluetoothMapAppParams ap) { 555 int read = 0; 556 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 557 read = c.getInt(fi.mSmsColRead); 558 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 559 read = c.getInt(fi.mMmsColRead); 560 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 561 read = c.getInt(fi.mMessageColRead); 562 } 563 String setRead = null; 564 565 Log.v(TAG, "setRead: " + setRead); 566 e.setRead((read == 1), ((ap.getParameterMask() & MASK_READ) != 0)); 567 } 568 setPriority( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)569 private static void setPriority( 570 BluetoothMapMessageListingElement e, 571 Cursor c, 572 FilterInfo fi, 573 BluetoothMapAppParams ap) { 574 if ((ap.getParameterMask() & MASK_PRIORITY) != 0) { 575 String priority = "no"; 576 if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 577 int highPriority = c.getInt(fi.mMessageColPriority); 578 if (highPriority == 1) { 579 priority = "yes"; 580 } 581 } 582 int pri = 0; 583 if (fi.mMsgType == FilterInfo.TYPE_MMS) { 584 pri = c.getInt(c.getColumnIndex(Mms.PRIORITY)); 585 } 586 if (pri == PduHeaders.PRIORITY_HIGH) { 587 priority = "yes"; 588 } 589 Log.v(TAG, "setPriority: " + priority); 590 e.setPriority(priority); 591 } 592 } 593 594 /** 595 * For SMS we set the attachment size to 0, as all data will be text data, hence attachments for 596 * SMS is not possible. For MMS all data is actually attachments, hence we do set the attachment 597 * size to the total message size. To provide a more accurate attachment size, one could extract 598 * the length (in bytes) of the text parts. 599 */ 600 @VisibleForTesting setAttachment( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)601 void setAttachment( 602 BluetoothMapMessageListingElement e, 603 Cursor c, 604 FilterInfo fi, 605 BluetoothMapAppParams ap) { 606 if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) { 607 int size = 0; 608 String attachmentMimeTypes = null; 609 if (fi.mMsgType == FilterInfo.TYPE_MMS) { 610 if (c.getInt(fi.mMmsColTextOnly) == 0) { 611 size = c.getInt(fi.mMmsColAttachmentSize); 612 if (size <= 0) { 613 // We know there are attachments, since it is not TextOnly 614 // Hence the size in the database must be wrong. 615 // Set size to 1 to indicate to the client, that attachments are present 616 Log.d( 617 TAG, 618 "Error in message database, size reported as: " 619 + size 620 + " Changing size to 1"); 621 size = 1; 622 } 623 // TODO: Add handling of attachment mime types 624 } 625 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 626 int attachment = c.getInt(fi.mMessageColAttachment); 627 size = c.getInt(fi.mMessageColAttachmentSize); 628 if (attachment == 1 && size == 0) { 629 Log.d( 630 TAG, 631 "Error in message database, attachment size reported as: " 632 + size 633 + " Changing size to 1"); 634 size = 1; /* Ensure we indicate we have attachments in the size, if the 635 message has attachments, in case the e-mail client do not 636 report a size */ 637 } 638 } else if (fi.mMsgType == FilterInfo.TYPE_IM) { 639 int attachment = c.getInt(fi.mMessageColAttachment); 640 size = c.getInt(fi.mMessageColAttachmentSize); 641 if (attachment == 1 && size == 0) { 642 size = 1; /* Ensure we indicate we have attachments in the size, it the 643 message has attachments, in case the e-mail client do not 644 report a size */ 645 attachmentMimeTypes = c.getString(fi.mMessageColAttachmentMime); 646 } 647 } 648 Log.v( 649 TAG, 650 "setAttachmentSize: " 651 + size 652 + "\n" 653 + "setAttachmentMimeTypes: " 654 + attachmentMimeTypes); 655 e.setAttachmentSize(size); 656 657 if ((mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10) 658 && ((ap.getParameterMask() & MASK_ATTACHMENT_MIME) != 0)) { 659 e.setAttachmentMimeTypes(attachmentMimeTypes); 660 } 661 } 662 } 663 setText( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)664 private void setText( 665 BluetoothMapMessageListingElement e, 666 Cursor c, 667 FilterInfo fi, 668 BluetoothMapAppParams ap) { 669 if ((ap.getParameterMask() & MASK_TEXT) != 0) { 670 String hasText = ""; 671 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 672 hasText = "yes"; 673 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 674 int textOnly = c.getInt(fi.mMmsColTextOnly); 675 if (textOnly == 1) { 676 hasText = "yes"; 677 } else { 678 long id = c.getLong(fi.mMmsColId); 679 String text = getTextPartsMms(mResolver, id); 680 if (text != null && text.length() > 0) { 681 hasText = "yes"; 682 } else { 683 hasText = "no"; 684 } 685 } 686 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 687 hasText = "yes"; 688 } 689 Log.v(TAG, "setText: " + hasText); 690 e.setText(hasText); 691 } 692 } 693 setReceptionStatus( BluetoothMapMessageListingElement e, BluetoothMapAppParams ap)694 private static void setReceptionStatus( 695 BluetoothMapMessageListingElement e, BluetoothMapAppParams ap) { 696 if ((ap.getParameterMask() & MASK_RECEPTION_STATUS) != 0) { 697 String status = "complete"; 698 Log.v(TAG, "setReceptionStatus: " + status); 699 e.setReceptionStatus(status); 700 } 701 } 702 703 @VisibleForTesting setDeliveryStatus( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)704 void setDeliveryStatus( 705 BluetoothMapMessageListingElement e, 706 Cursor c, 707 FilterInfo fi, 708 BluetoothMapAppParams ap) { 709 if ((ap.getParameterMask() & MASK_DELIVERY_STATUS) != 0) { 710 String deliveryStatus = "delivered"; 711 // TODO: Should be handled for SMS and MMS as well 712 if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 713 deliveryStatus = c.getString(fi.mMessageColDelivery); 714 } 715 Log.v(TAG, "setDeliveryStatus: " + deliveryStatus); 716 e.setDeliveryStatus(deliveryStatus); 717 } 718 } 719 setSize( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)720 private void setSize( 721 BluetoothMapMessageListingElement e, 722 Cursor c, 723 FilterInfo fi, 724 BluetoothMapAppParams ap) { 725 if ((ap.getParameterMask() & MASK_SIZE) != 0) { 726 int size = 0; 727 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 728 String subject = c.getString(fi.mSmsColSubject); 729 size = subject.length(); 730 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 731 size = c.getInt(fi.mMmsColSize); 732 // MMS complete size = attachment_size + subject length 733 String subject = e.getSubject(); 734 if (subject == null || subject.length() == 0) { 735 // Handle setSubject if not done case 736 setSubject(e, c, fi, ap); 737 } 738 if (subject != null && subject.length() != 0) { 739 size += subject.length(); 740 } 741 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 742 size = c.getInt(fi.mMessageColSize); 743 } 744 if (size <= 0) { 745 // A message cannot have size 0 746 // Hence the size in the database must be wrong. 747 // Set size to 1 to indicate to the client, that the message has content. 748 Log.d( 749 TAG, 750 "Error in message database, size reported as: " 751 + size 752 + " Changing size to 1"); 753 size = 1; 754 } 755 Log.v(TAG, "setSize: " + size); 756 e.setSize(size); 757 } 758 } 759 getType(FilterInfo fi)760 private static TYPE getType(FilterInfo fi) { 761 TYPE type = null; 762 Log.d(TAG, "getType: for filterMsgType" + fi.mMsgType); 763 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 764 Log.d(TAG, "getType: phoneType for SMS " + fi.mPhoneType); 765 if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_CDMA) { 766 type = TYPE.SMS_CDMA; 767 } else { 768 type = TYPE.SMS_GSM; 769 } 770 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 771 type = TYPE.MMS; 772 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 773 type = TYPE.EMAIL; 774 } else if (fi.mMsgType == FilterInfo.TYPE_IM) { 775 type = TYPE.IM; 776 } 777 Log.v(TAG, "getType: " + type); 778 779 return type; 780 } 781 782 @VisibleForTesting getRecipientNameEmail(Cursor c, FilterInfo fi)783 String getRecipientNameEmail(Cursor c, FilterInfo fi) { 784 785 String toAddress, ccAddress, bccAddress; 786 toAddress = c.getString(fi.mMessageColToAddress); 787 ccAddress = c.getString(fi.mMessageColCcAddress); 788 bccAddress = c.getString(fi.mMessageColBccAddress); 789 790 StringBuilder sb = new StringBuilder(); 791 if (toAddress != null) { 792 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(toAddress); 793 if (tokens.length != 0) { 794 Log.d(TAG, "toName count= " + tokens.length); 795 int i = 0; 796 boolean first = true; 797 while (i < tokens.length) { 798 Log.v(TAG, "ToName = " + tokens[i].toString()); 799 String name = tokens[i].getName(); 800 if (!first) { 801 sb.append("; "); // Delimiter 802 } 803 sb.append(name); 804 first = false; 805 i++; 806 } 807 } 808 809 if (ccAddress != null) { 810 sb.append("; "); 811 } 812 } 813 if (ccAddress != null) { 814 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(ccAddress); 815 if (tokens.length != 0) { 816 Log.d(TAG, "ccName count= " + tokens.length); 817 int i = 0; 818 boolean first = true; 819 while (i < tokens.length) { 820 Log.v(TAG, "ccName = " + tokens[i].toString()); 821 String name = tokens[i].getName(); 822 if (!first) { 823 sb.append("; "); // Delimiter 824 } 825 sb.append(name); 826 first = false; 827 i++; 828 } 829 } 830 if (bccAddress != null) { 831 sb.append("; "); 832 } 833 } 834 if (bccAddress != null) { 835 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(bccAddress); 836 if (tokens.length != 0) { 837 Log.d(TAG, "bccName count= " + tokens.length); 838 int i = 0; 839 boolean first = true; 840 while (i < tokens.length) { 841 Log.v(TAG, "bccName = " + tokens[i].toString()); 842 String name = tokens[i].getName(); 843 if (!first) { 844 sb.append("; "); // Delimiter 845 } 846 sb.append(name); 847 first = false; 848 i++; 849 } 850 } 851 } 852 return sb.toString(); 853 } 854 855 @VisibleForTesting getRecipientAddressingEmail(Cursor c, FilterInfo fi)856 String getRecipientAddressingEmail(Cursor c, FilterInfo fi) { 857 String toAddress, ccAddress, bccAddress; 858 toAddress = c.getString(fi.mMessageColToAddress); 859 ccAddress = c.getString(fi.mMessageColCcAddress); 860 bccAddress = c.getString(fi.mMessageColBccAddress); 861 862 StringBuilder sb = new StringBuilder(); 863 if (toAddress != null) { 864 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(toAddress); 865 if (tokens.length != 0) { 866 Log.d(TAG, "toAddress count= " + tokens.length); 867 int i = 0; 868 boolean first = true; 869 while (i < tokens.length) { 870 Log.v(TAG, "ToAddress = " + tokens[i].toString()); 871 String email = tokens[i].getAddress(); 872 if (!first) { 873 sb.append("; "); // Delimiter 874 } 875 sb.append(email); 876 first = false; 877 i++; 878 } 879 } 880 881 if (ccAddress != null) { 882 sb.append("; "); 883 } 884 } 885 if (ccAddress != null) { 886 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(ccAddress); 887 if (tokens.length != 0) { 888 Log.d(TAG, "ccAddress count= " + tokens.length); 889 int i = 0; 890 boolean first = true; 891 while (i < tokens.length) { 892 Log.v(TAG, "ccAddress = " + tokens[i].toString()); 893 String email = tokens[i].getAddress(); 894 if (!first) { 895 sb.append("; "); // Delimiter 896 } 897 sb.append(email); 898 first = false; 899 i++; 900 } 901 } 902 if (bccAddress != null) { 903 sb.append("; "); 904 } 905 } 906 if (bccAddress != null) { 907 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(bccAddress); 908 if (tokens.length != 0) { 909 Log.d(TAG, "bccAddress count= " + tokens.length); 910 int i = 0; 911 boolean first = true; 912 while (i < tokens.length) { 913 Log.v(TAG, "bccAddress = " + tokens[i].toString()); 914 String email = tokens[i].getAddress(); 915 if (!first) { 916 sb.append("; "); // Delimiter 917 } 918 sb.append(email); 919 first = false; 920 i++; 921 } 922 } 923 } 924 return sb.toString(); 925 } 926 927 @VisibleForTesting setRecipientAddressing( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)928 void setRecipientAddressing( 929 BluetoothMapMessageListingElement e, 930 Cursor c, 931 FilterInfo fi, 932 BluetoothMapAppParams ap) { 933 if ((ap.getParameterMask() & MASK_RECIPIENT_ADDRESSING) != 0) { 934 String address = null; 935 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 936 int msgType = c.getInt(fi.mSmsColType); 937 if (msgType == Sms.MESSAGE_TYPE_INBOX) { 938 address = fi.mPhoneNum; 939 } else { 940 address = c.getString(c.getColumnIndex(Sms.ADDRESS)); 941 } 942 if ((address == null) && msgType == Sms.MESSAGE_TYPE_DRAFT) { 943 // Fetch address for Drafts folder from "canonical_address" table 944 int threadIdInd = c.getColumnIndex(Sms.THREAD_ID); 945 String threadIdStr = c.getString(threadIdInd); 946 // If a draft message has no recipient, it has no thread ID 947 // hence threadIdStr could possibly be null 948 if (threadIdStr != null) { 949 address = getCanonicalAddressSms(mResolver, Integer.valueOf(threadIdStr)); 950 } 951 Log.v(TAG, "threadId = " + threadIdStr + " address:" + address + "\n"); 952 } 953 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 954 long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); 955 address = getAddressMms(mResolver, id, MMS_TO); 956 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 957 /* Might be another way to handle addresses */ 958 address = getRecipientAddressingEmail(c, fi); 959 } 960 Log.v(TAG, "setRecipientAddressing: " + address); 961 if (address == null) { 962 address = ""; 963 } 964 e.setRecipientAddressing(address); 965 } 966 } 967 setRecipientName( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)968 private void setRecipientName( 969 BluetoothMapMessageListingElement e, 970 Cursor c, 971 FilterInfo fi, 972 BluetoothMapAppParams ap) { 973 if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) { 974 String name = null; 975 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 976 int msgType = c.getInt(fi.mSmsColType); 977 if (msgType != 1) { 978 String phone = c.getString(fi.mSmsColAddress); 979 if (phone != null && !phone.isEmpty()) { 980 name = getContactNameFromPhone(phone, mResolver); 981 } 982 } else { 983 name = fi.mPhoneAlphaTag; 984 } 985 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 986 long id = c.getLong(fi.mMmsColId); 987 String phone; 988 if (e.getRecipientAddressing() != null) { 989 phone = getAddressMms(mResolver, id, MMS_TO); 990 } else { 991 phone = e.getRecipientAddressing(); 992 } 993 if (phone != null && !phone.isEmpty()) { 994 name = getContactNameFromPhone(phone, mResolver); 995 } 996 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 997 /* Might be another way to handle address and names */ 998 name = getRecipientNameEmail(c, fi); 999 } 1000 Log.v(TAG, "setRecipientName: " + name); 1001 if (name == null) { 1002 name = ""; 1003 } 1004 e.setRecipientName(name); 1005 } 1006 } 1007 1008 @VisibleForTesting setSenderAddressing( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1009 void setSenderAddressing( 1010 BluetoothMapMessageListingElement e, 1011 Cursor c, 1012 FilterInfo fi, 1013 BluetoothMapAppParams ap) { 1014 if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) { 1015 String address = ""; 1016 String tempAddress; 1017 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1018 int msgType = c.getInt(fi.mSmsColType); 1019 if (msgType == 1) { // INBOX 1020 tempAddress = c.getString(fi.mSmsColAddress); 1021 } else { 1022 tempAddress = fi.mPhoneNum; 1023 } 1024 if (tempAddress == null) { 1025 /* This can only happen on devices with no SIM - 1026 hence will typically not have any SMS messages. */ 1027 } else { 1028 address = PhoneNumberUtils.extractNetworkPortion(tempAddress); 1029 /* extractNetworkPortion can return N if the number is a service "number" = 1030 * a string with the a name in (i.e. "Some-Tele-company" would return N 1031 * because of the N in compaNy) 1032 * Hence we need to check if the number is actually a string with alpha chars. 1033 * */ 1034 Boolean alpha = 1035 PhoneNumberUtils.stripSeparators(tempAddress) 1036 .matches("[0-9]*[a-zA-Z]+[0-9]*"); 1037 1038 if (address == null || address.length() < 2 || alpha) { 1039 address = tempAddress; // if the number is a service ascii text just use it 1040 } 1041 } 1042 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1043 long id = c.getLong(fi.mMmsColId); 1044 tempAddress = getAddressMms(mResolver, id, MMS_FROM); 1045 address = PhoneNumberUtils.extractNetworkPortion(tempAddress); 1046 if (address == null || address.length() < 1) { 1047 address = tempAddress; // if the number is a service ascii text just use it 1048 } 1049 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL /* || 1050 fi.mMsgType == FilterInfo.TYPE_IM*/) { 1051 String nameEmail = c.getString(fi.mMessageColFromAddress); 1052 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail); 1053 Log.d(TAG, "Originator count= " + tokens.length); 1054 address = 1055 Arrays.stream(tokens) 1056 .peek(token -> Log.v(TAG, "SenderAddress=" + token)) 1057 .map(Rfc822Token::getAddress) 1058 .collect(Collectors.joining("; ")); 1059 } else if (fi.mMsgType == FilterInfo.TYPE_IM) { 1060 // TODO: For IM we add the contact ID in the addressing 1061 long contactId = c.getLong(fi.mMessageColFromAddress); 1062 // TODO: This is a BAD hack, that we map the contact ID to a conversation ID!!! 1063 // We need to reach a conclusion on what to do 1064 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); 1065 Cursor contacts = 1066 BluetoothMethodProxy.getInstance() 1067 .contentResolverQuery( 1068 mResolver, 1069 contactsUri, 1070 BluetoothMapContract.BT_CONTACT_PROJECTION, 1071 BluetoothMapContract.ConvoContactColumns.CONVO_ID 1072 + " = " 1073 + contactId, 1074 null, 1075 null); 1076 try { 1077 // TODO this will not work for group-chats 1078 if (contacts != null && contacts.moveToFirst()) { 1079 address = 1080 contacts.getString( 1081 contacts.getColumnIndex( 1082 BluetoothMapContract.ConvoContactColumns.UCI)); 1083 } 1084 } finally { 1085 if (contacts != null) { 1086 contacts.close(); 1087 } 1088 } 1089 } 1090 Log.v(TAG, "setSenderAddressing: " + address); 1091 if (address == null) { 1092 address = ""; 1093 } 1094 e.setSenderAddressing(address); 1095 } 1096 } 1097 1098 @VisibleForTesting setSenderName( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1099 void setSenderName( 1100 BluetoothMapMessageListingElement e, 1101 Cursor c, 1102 FilterInfo fi, 1103 BluetoothMapAppParams ap) { 1104 if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) { 1105 String name = ""; 1106 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1107 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); 1108 if (msgType == 1) { 1109 String phone = c.getString(fi.mSmsColAddress); 1110 if (phone != null && !phone.isEmpty()) { 1111 name = getContactNameFromPhone(phone, mResolver); 1112 } 1113 } else { 1114 name = fi.mPhoneAlphaTag; 1115 } 1116 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1117 long id = c.getLong(fi.mMmsColId); 1118 String phone; 1119 if (e.getSenderAddressing() != null) { 1120 phone = getAddressMms(mResolver, id, MMS_FROM); 1121 } else { 1122 phone = e.getSenderAddressing(); 1123 } 1124 if (phone != null && !phone.isEmpty()) { 1125 name = getContactNameFromPhone(phone, mResolver); 1126 } 1127 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL /* || 1128 fi.mMsgType == FilterInfo.TYPE_IM*/) { 1129 String nameEmail = c.getString(fi.mMessageColFromAddress); 1130 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail); 1131 Log.d(TAG, "Originator count= " + tokens.length); 1132 name = 1133 Arrays.stream(tokens) 1134 .peek(token -> Log.v(TAG, "SenderName=" + token)) 1135 .map(Rfc822Token::getName) 1136 .collect(Collectors.joining("; ")); 1137 } else if (fi.mMsgType == FilterInfo.TYPE_IM) { 1138 // For IM we add the contact ID in the addressing 1139 long contactId = c.getLong(fi.mMessageColFromAddress); 1140 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); 1141 Cursor contacts = 1142 BluetoothMethodProxy.getInstance() 1143 .contentResolverQuery( 1144 mResolver, 1145 contactsUri, 1146 BluetoothMapContract.BT_CONTACT_PROJECTION, 1147 BluetoothMapContract.ConvoContactColumns.CONVO_ID 1148 + " = " 1149 + contactId, 1150 null, 1151 null); 1152 try { 1153 // TODO this will not work for group-chats 1154 if (contacts != null && contacts.moveToFirst()) { 1155 name = 1156 contacts.getString( 1157 contacts.getColumnIndex( 1158 BluetoothMapContract.ConvoContactColumns.NAME)); 1159 } 1160 } finally { 1161 if (contacts != null) { 1162 contacts.close(); 1163 } 1164 } 1165 } 1166 Log.v(TAG, "setSenderName: " + name); 1167 if (name == null) { 1168 name = ""; 1169 } 1170 e.setSenderName(name); 1171 } 1172 } 1173 1174 @VisibleForTesting setDateTime( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1175 void setDateTime( 1176 BluetoothMapMessageListingElement e, 1177 Cursor c, 1178 FilterInfo fi, 1179 BluetoothMapAppParams ap) { 1180 if ((ap.getParameterMask() & MASK_DATETIME) != 0) { 1181 long date = 0; 1182 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1183 date = c.getLong(fi.mSmsColDate); 1184 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1185 /* Use Mms.DATE for all messages. Although contract class states */ 1186 /* Mms.DATE_SENT are for outgoing messages. But that is not working. */ 1187 date = c.getLong(fi.mMmsColDate) * 1000L; 1188 1189 /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */ 1190 /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */ 1191 /* date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */ 1192 /* } else { */ 1193 /* date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */ 1194 /* } */ 1195 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1196 date = c.getLong(fi.mMessageColDate); 1197 } 1198 e.setDateTime(date); 1199 } 1200 } 1201 1202 @VisibleForTesting setLastActivity(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi)1203 void setLastActivity(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi) { 1204 long date = 0; 1205 if (fi.mMsgType == FilterInfo.TYPE_SMS || fi.mMsgType == FilterInfo.TYPE_MMS) { 1206 date = c.getLong(MMS_SMS_THREAD_COL_DATE); 1207 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1208 date = c.getLong(fi.mConvoColLastActivity); 1209 } 1210 e.setLastActivity(date); 1211 Log.v(TAG, "setDateTime: " + e.getLastActivityString()); 1212 } 1213 getTextPartsMms(ContentResolver r, long id)1214 public static String getTextPartsMms(ContentResolver r, long id) { 1215 StringBuilder textBuilder = new StringBuilder(); 1216 String selection = new String("mid=" + id); 1217 String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part"); 1218 Uri uriAddress = Uri.parse(uriStr); 1219 // TODO: maybe use a projection with only "ct" and "text" 1220 Cursor c = 1221 BluetoothMethodProxy.getInstance() 1222 .contentResolverQuery(r, uriAddress, null, selection, null, null); 1223 try { 1224 if (c != null && c.moveToFirst()) { 1225 do { 1226 String ct = c.getString(c.getColumnIndex("ct")); 1227 if (ct.equals("text/plain")) { 1228 String part = c.getString(c.getColumnIndex("text")); 1229 if (part != null) { 1230 textBuilder.append(part); 1231 } 1232 } 1233 } while (c.moveToNext()); 1234 } 1235 } finally { 1236 if (c != null) { 1237 c.close(); 1238 } 1239 } 1240 1241 return textBuilder.toString(); 1242 } 1243 setSubject( BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1244 private void setSubject( 1245 BluetoothMapMessageListingElement e, 1246 Cursor c, 1247 FilterInfo fi, 1248 BluetoothMapAppParams ap) { 1249 String subject = ""; 1250 int subLength = ap.getSubjectLength(); 1251 if (subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1252 subLength = 256; 1253 } 1254 1255 // Fix Subject Display issue with HONDA Carkit - Ignore subject Mask. 1256 boolean isHondaCarkit; 1257 if (Utils.isInstrumentationTestMode()) { 1258 isHondaCarkit = false; 1259 } else { 1260 isHondaCarkit = 1261 DeviceWorkArounds.addressStartsWith( 1262 BluetoothMapService.getBluetoothMapService() 1263 .getRemoteDevice() 1264 .getAddress(), 1265 DeviceWorkArounds.HONDA_CARKIT); 1266 } 1267 if (isHondaCarkit || (ap.getParameterMask() & MASK_SUBJECT) != 0) { 1268 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1269 subject = c.getString(fi.mSmsColSubject); 1270 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1271 subject = c.getString(fi.mMmsColSubject); 1272 if (subject == null || subject.length() == 0) { 1273 /* Get subject from mms text body parts - if any exists */ 1274 long id = c.getLong(fi.mMmsColId); 1275 subject = getTextPartsMms(mResolver, id); 1276 } 1277 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1278 subject = c.getString(fi.mMessageColSubject); 1279 } 1280 if (subject != null && subject.length() > subLength) { 1281 subject = subject.substring(0, subLength); 1282 } else if (subject == null) { 1283 subject = ""; 1284 } 1285 Log.v(TAG, "setSubject: " + subject); 1286 e.setSubject(subject); 1287 } 1288 } 1289 setHandle(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi)1290 private static void setHandle(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi) { 1291 long handle = -1; 1292 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1293 handle = c.getLong(fi.mSmsColId); 1294 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1295 handle = c.getLong(fi.mMmsColId); 1296 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1297 handle = c.getLong(fi.mMessageColId); 1298 } 1299 Log.v(TAG, "setHandle: " + handle); 1300 e.setHandle(handle); 1301 } 1302 element( Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1303 private BluetoothMapMessageListingElement element( 1304 Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { 1305 BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement(); 1306 setHandle(e, c, fi); 1307 setDateTime(e, c, fi, ap); 1308 e.setType(getType(fi), (ap.getParameterMask() & MASK_TYPE) != 0); 1309 setRead(e, c, fi, ap); 1310 // we set number and name for sender/recipient later 1311 // they require lookup on contacts so no need to 1312 // do it for all elements unless they are to be used. 1313 e.setCursorIndex(c.getPosition()); 1314 return e; 1315 } 1316 createConvoElement(Cursor c, FilterInfo fi)1317 private BluetoothMapConvoListingElement createConvoElement(Cursor c, FilterInfo fi) { 1318 BluetoothMapConvoListingElement e = new BluetoothMapConvoListingElement(); 1319 setLastActivity(e, c, fi); 1320 e.setType(getType(fi)); 1321 e.setCursorIndex(c.getPosition()); 1322 return e; 1323 } 1324 1325 /* TODO: Change to use SmsMmsContacts.getContactNameFromPhone() with proper use of 1326 * caching. */ getContactNameFromPhone(String phone, ContentResolver resolver)1327 public static String getContactNameFromPhone(String phone, ContentResolver resolver) { 1328 String name = null; 1329 // Handle possible exception for empty phone address 1330 if (TextUtils.isEmpty(phone)) { 1331 return name; 1332 } 1333 1334 Uri uri = 1335 Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone)); 1336 1337 String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME}; 1338 String selection = Contacts.IN_VISIBLE_GROUP + "=1"; 1339 String orderBy = Contacts.DISPLAY_NAME + " ASC"; 1340 Cursor c = null; 1341 try { 1342 c = 1343 BluetoothMethodProxy.getInstance() 1344 .contentResolverQuery( 1345 resolver, uri, projection, selection, null, orderBy); 1346 if (c != null) { 1347 int colIndex = c.getColumnIndex(Contacts.DISPLAY_NAME); 1348 if (c.getCount() >= 1) { 1349 c.moveToFirst(); 1350 name = c.getString(colIndex); 1351 } 1352 } 1353 } finally { 1354 if (c != null) { 1355 c.close(); 1356 } 1357 } 1358 return name; 1359 } 1360 1361 private static final String[] RECIPIENT_ID_PROJECTION = {Threads.RECIPIENT_IDS}; 1362 1363 /** Get SMS RecipientAddresses for DRAFT folder based on threadId */ getCanonicalAddressSms(ContentResolver r, int threadId)1364 public static String getCanonicalAddressSms(ContentResolver r, int threadId) { 1365 1366 /* 1367 1. Get Recipient Ids from Threads.CONTENT_URI 1368 2. Get Recipient Address for corresponding Id from canonical-addresses table. 1369 */ 1370 1371 // Uri sAllCanonical = Uri.parse("content://mms-sms/canonical-addresses"); 1372 Uri sAllCanonical = 1373 MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build(); 1374 Uri sAllThreadsUri = 1375 Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); 1376 Cursor cr = null; 1377 String recipientIds = null; 1378 String whereClause = "_id=" + threadId; 1379 Log.v(TAG, "whereClause is " + whereClause); 1380 try { 1381 cr = 1382 BluetoothMethodProxy.getInstance() 1383 .contentResolverQuery( 1384 r, 1385 sAllThreadsUri, 1386 RECIPIENT_ID_PROJECTION, 1387 whereClause, 1388 null, 1389 null); 1390 if (cr != null && cr.moveToFirst()) { 1391 recipientIds = cr.getString(0); 1392 Log.v( 1393 TAG, 1394 "cursor.getCount(): " 1395 + cr.getCount() 1396 + "recipientIds: " 1397 + recipientIds 1398 + "selection: " 1399 + whereClause); 1400 } 1401 } finally { 1402 if (cr != null) { 1403 cr.close(); 1404 cr = null; 1405 } 1406 } 1407 Log.v(TAG, "recipientIds with spaces: " + recipientIds + "\n"); 1408 StringBuilder recipientAddress = new StringBuilder(); 1409 if (recipientIds != null) { 1410 whereClause = 1411 Arrays.stream(recipientIds.split(" ")) 1412 .map(id -> "_id=" + id) 1413 .collect(Collectors.joining(" OR ")); 1414 Log.v(TAG, "whereClause is " + whereClause); 1415 try { 1416 cr = 1417 BluetoothMethodProxy.getInstance() 1418 .contentResolverQuery( 1419 r, sAllCanonical, null, whereClause, null, null); 1420 if (cr != null && cr.moveToFirst()) { 1421 do { 1422 // TODO: Multiple Recipients are appended with ";" for now. 1423 if (recipientAddress.length() != 0) { 1424 recipientAddress.append(";"); 1425 } 1426 recipientAddress.append( 1427 cr.getString(cr.getColumnIndex(CanonicalAddressesColumns.ADDRESS))); 1428 } while (cr.moveToNext()); 1429 } 1430 } finally { 1431 if (cr != null) { 1432 cr.close(); 1433 } 1434 } 1435 } 1436 1437 Log.v(TAG, "Final recipientAddress: " + recipientAddress.toString()); 1438 return recipientAddress.toString(); 1439 } 1440 getAddressMms(ContentResolver r, long id, int type)1441 public static String getAddressMms(ContentResolver r, long id, int type) { 1442 String selection = new String("msg_id=" + id + " AND type=" + type); 1443 String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr"); 1444 Uri uriAddress = Uri.parse(uriStr); 1445 String addr = null; 1446 String[] projection = {Mms.Addr.ADDRESS}; 1447 Cursor c = null; 1448 try { 1449 c = 1450 BluetoothMethodProxy.getInstance() 1451 .contentResolverQuery( 1452 r, 1453 uriAddress, 1454 projection, 1455 selection, 1456 null, 1457 null); // TODO: Add projection 1458 int colIndex = c.getColumnIndex(Mms.Addr.ADDRESS); 1459 if (c != null) { 1460 if (c.moveToFirst()) { 1461 addr = c.getString(colIndex); 1462 if (INSERT_ADDRESS_TOKEN.equals(addr)) { 1463 addr = ""; 1464 } 1465 } 1466 } 1467 } finally { 1468 if (c != null) { 1469 c.close(); 1470 } 1471 } 1472 return addr; 1473 } 1474 1475 /** 1476 * Matching functions for originator and recipient for MMS 1477 * 1478 * @return true if found a match 1479 */ matchRecipientMms(Cursor c, String recipient)1480 private boolean matchRecipientMms(Cursor c, String recipient) { 1481 boolean res; 1482 long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); 1483 String phone = getAddressMms(mResolver, id, MMS_TO); 1484 if (phone != null && phone.length() > 0) { 1485 if (phone.matches(recipient)) { 1486 Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone); 1487 res = true; 1488 } else { 1489 String name = getContactNameFromPhone(phone, mResolver); 1490 if (name != null && name.length() > 0 && name.matches(recipient)) { 1491 Log.v(TAG, "matchRecipientMms: match recipient name = " + name); 1492 res = true; 1493 } else { 1494 res = false; 1495 } 1496 } 1497 } else { 1498 res = false; 1499 } 1500 return res; 1501 } 1502 matchRecipientSms(Cursor c, FilterInfo fi, String recipient)1503 private boolean matchRecipientSms(Cursor c, FilterInfo fi, String recipient) { 1504 boolean res; 1505 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); 1506 if (msgType == 1) { 1507 String phone = fi.mPhoneNum; 1508 String name = fi.mPhoneAlphaTag; 1509 if (phone != null && phone.length() > 0 && phone.matches(recipient)) { 1510 Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone); 1511 res = true; 1512 } else if (name != null && name.length() > 0 && name.matches(recipient)) { 1513 Log.v(TAG, "matchRecipientSms: match recipient name = " + name); 1514 res = true; 1515 } else { 1516 res = false; 1517 } 1518 } else { 1519 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); 1520 if (phone != null && phone.length() > 0) { 1521 if (phone.matches(recipient)) { 1522 Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone); 1523 res = true; 1524 } else { 1525 String name = getContactNameFromPhone(phone, mResolver); 1526 if (name != null && name.length() > 0 && name.matches(recipient)) { 1527 Log.v(TAG, "matchRecipientSms: match recipient name = " + name); 1528 res = true; 1529 } else { 1530 res = false; 1531 } 1532 } 1533 } else { 1534 res = false; 1535 } 1536 } 1537 return res; 1538 } 1539 matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1540 private boolean matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { 1541 boolean res; 1542 String recipient = ap.getFilterRecipient(); 1543 if (recipient != null && recipient.length() > 0) { 1544 recipient = recipient.replace("*", ".*"); 1545 recipient = ".*" + recipient + ".*"; 1546 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1547 res = matchRecipientSms(c, fi, recipient); 1548 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1549 res = matchRecipientMms(c, recipient); 1550 } else { 1551 Log.d(TAG, "matchRecipient: Unknown msg type: " + fi.mMsgType); 1552 res = false; 1553 } 1554 } else { 1555 res = true; 1556 } 1557 return res; 1558 } 1559 matchOriginatorMms(Cursor c, String orig)1560 private boolean matchOriginatorMms(Cursor c, String orig) { 1561 boolean res; 1562 long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); 1563 String phone = getAddressMms(mResolver, id, MMS_FROM); 1564 if (phone != null && phone.length() > 0) { 1565 if (phone.matches(orig)) { 1566 Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone); 1567 res = true; 1568 } else { 1569 String name = getContactNameFromPhone(phone, mResolver); 1570 if (name != null && name.length() > 0 && name.matches(orig)) { 1571 Log.v(TAG, "matchOriginatorMms: match originator name = " + name); 1572 res = true; 1573 } else { 1574 res = false; 1575 } 1576 } 1577 } else { 1578 res = false; 1579 } 1580 return res; 1581 } 1582 matchOriginatorSms(Cursor c, FilterInfo fi, String orig)1583 private boolean matchOriginatorSms(Cursor c, FilterInfo fi, String orig) { 1584 boolean res; 1585 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); 1586 if (msgType == 1) { 1587 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); 1588 if (phone != null && phone.length() > 0) { 1589 if (phone.matches(orig)) { 1590 Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone); 1591 res = true; 1592 } else { 1593 String name = getContactNameFromPhone(phone, mResolver); 1594 if (name != null && name.length() > 0 && name.matches(orig)) { 1595 Log.v(TAG, "matchOriginatorSms: match originator name = " + name); 1596 res = true; 1597 } else { 1598 res = false; 1599 } 1600 } 1601 } else { 1602 res = false; 1603 } 1604 } else { 1605 String phone = fi.mPhoneNum; 1606 String name = fi.mPhoneAlphaTag; 1607 if (phone != null && phone.length() > 0 && phone.matches(orig)) { 1608 Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone); 1609 res = true; 1610 } else if (name != null && name.length() > 0 && name.matches(orig)) { 1611 Log.v(TAG, "matchOriginatorSms: match originator name = " + name); 1612 res = true; 1613 } else { 1614 res = false; 1615 } 1616 } 1617 return res; 1618 } 1619 matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1620 private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { 1621 boolean res; 1622 String orig = ap.getFilterOriginator(); 1623 if (orig != null && orig.length() > 0) { 1624 orig = orig.replace("*", ".*"); 1625 orig = ".*" + orig + ".*"; 1626 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1627 res = matchOriginatorSms(c, fi, orig); 1628 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1629 res = matchOriginatorMms(c, orig); 1630 } else { 1631 Log.d(TAG, "matchOriginator: Unknown msg type: " + fi.mMsgType); 1632 res = false; 1633 } 1634 } else { 1635 res = true; 1636 } 1637 return res; 1638 } 1639 matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1640 private boolean matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { 1641 return matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap); 1642 } 1643 1644 /* Where filter functions */ setWhereFilterFolderTypeSms(String folder)1645 private static String setWhereFilterFolderTypeSms(String folder) { 1646 String where = ""; 1647 if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) { 1648 where = Sms.TYPE + " = 1 AND " + Sms.THREAD_ID + " <> -1"; 1649 } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) { 1650 where = 1651 "(" 1652 + Sms.TYPE 1653 + " = 4 OR " 1654 + Sms.TYPE 1655 + " = 5 OR " 1656 + Sms.TYPE 1657 + " = 6) AND " 1658 + Sms.THREAD_ID 1659 + " <> -1"; 1660 } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) { 1661 where = Sms.TYPE + " = 2 AND " + Sms.THREAD_ID + " <> -1"; 1662 } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) { 1663 where = 1664 Sms.TYPE 1665 + " = 3 AND " 1666 + "(" 1667 + Sms.THREAD_ID 1668 + " IS NULL OR " 1669 + Sms.THREAD_ID 1670 + " <> -1 )"; 1671 } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) { 1672 where = Sms.THREAD_ID + " = -1"; 1673 } 1674 1675 return where; 1676 } 1677 setWhereFilterFolderTypeMms(String folder)1678 private static String setWhereFilterFolderTypeMms(String folder) { 1679 String where = ""; 1680 if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) { 1681 where = Mms.MESSAGE_BOX + " = 1 AND " + Mms.THREAD_ID + " <> -1"; 1682 } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) { 1683 where = Mms.MESSAGE_BOX + " = 4 AND " + Mms.THREAD_ID + " <> -1"; 1684 } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) { 1685 where = Mms.MESSAGE_BOX + " = 2 AND " + Mms.THREAD_ID + " <> -1"; 1686 } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) { 1687 where = 1688 Mms.MESSAGE_BOX 1689 + " = 3 AND " 1690 + "(" 1691 + Mms.THREAD_ID 1692 + " IS NULL OR " 1693 + Mms.THREAD_ID 1694 + " <> -1 )"; 1695 } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) { 1696 where = Mms.THREAD_ID + " = -1"; 1697 } 1698 1699 return where; 1700 } 1701 setWhereFilterFolderTypeEmail(long folderId)1702 private static String setWhereFilterFolderTypeEmail(long folderId) { 1703 String where = ""; 1704 if (folderId >= 0) { 1705 where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId; 1706 } else { 1707 Log.e(TAG, "setWhereFilterFolderTypeEmail: not valid!"); 1708 ContentProfileErrorReportUtils.report( 1709 BluetoothProfile.MAP, 1710 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 1711 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 1712 1); 1713 throw new IllegalArgumentException("Invalid folder ID"); 1714 } 1715 return where; 1716 } 1717 setWhereFilterFolderTypeIm(long folderId)1718 private static String setWhereFilterFolderTypeIm(long folderId) { 1719 String where = ""; 1720 if (folderId > BluetoothMapContract.FOLDER_ID_OTHER) { 1721 where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId; 1722 } else { 1723 Log.e(TAG, "setWhereFilterFolderTypeIm: not valid!"); 1724 ContentProfileErrorReportUtils.report( 1725 BluetoothProfile.MAP, 1726 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 1727 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 1728 2); 1729 throw new IllegalArgumentException("Invalid folder ID"); 1730 } 1731 return where; 1732 } 1733 setWhereFilterFolderType( BluetoothMapFolderElement folderElement, FilterInfo fi)1734 private static String setWhereFilterFolderType( 1735 BluetoothMapFolderElement folderElement, FilterInfo fi) { 1736 String where = "1=1"; 1737 if (!folderElement.shouldIgnore()) { 1738 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1739 where = setWhereFilterFolderTypeSms(folderElement.getName()); 1740 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1741 where = setWhereFilterFolderTypeMms(folderElement.getName()); 1742 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 1743 where = setWhereFilterFolderTypeEmail(folderElement.getFolderId()); 1744 } else if (fi.mMsgType == FilterInfo.TYPE_IM) { 1745 where = setWhereFilterFolderTypeIm(folderElement.getFolderId()); 1746 } 1747 } 1748 1749 return where; 1750 } 1751 setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi)1752 private static String setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi) { 1753 String where = ""; 1754 if (ap.getFilterReadStatus() != -1) { 1755 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1756 if ((ap.getFilterReadStatus() & 0x01) != 0) { 1757 where = " AND " + Sms.READ + "= 0"; 1758 } 1759 1760 if ((ap.getFilterReadStatus() & 0x02) != 0) { 1761 where = " AND " + Sms.READ + "= 1"; 1762 } 1763 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1764 if ((ap.getFilterReadStatus() & 0x01) != 0) { 1765 where = " AND " + Mms.READ + "= 0"; 1766 } 1767 1768 if ((ap.getFilterReadStatus() & 0x02) != 0) { 1769 where = " AND " + Mms.READ + "= 1"; 1770 } 1771 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1772 if ((ap.getFilterReadStatus() & 0x01) != 0) { 1773 where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0"; 1774 } 1775 if ((ap.getFilterReadStatus() & 0x02) != 0) { 1776 where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 1"; 1777 } 1778 } 1779 } 1780 return where; 1781 } 1782 setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi)1783 private static String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) { 1784 String where = ""; 1785 1786 if ((ap.getFilterPeriodBegin() != -1)) { 1787 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1788 where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin(); 1789 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1790 where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L); 1791 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1792 where = 1793 " AND " 1794 + BluetoothMapContract.MessageColumns.DATE 1795 + " >= " 1796 + (ap.getFilterPeriodBegin()); 1797 } 1798 } 1799 1800 if ((ap.getFilterPeriodEnd() != -1)) { 1801 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1802 where = where + " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd(); 1803 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1804 where = where + " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L); 1805 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1806 where = 1807 where 1808 + " AND " 1809 + BluetoothMapContract.MessageColumns.DATE 1810 + " < " 1811 + ap.getFilterPeriodEnd(); 1812 } 1813 } 1814 return where; 1815 } 1816 setWhereFilterOriginatorEmail(BluetoothMapAppParams ap)1817 private static String setWhereFilterOriginatorEmail(BluetoothMapAppParams ap) { 1818 String where = ""; 1819 String orig = ap.getFilterOriginator(); 1820 1821 /* Be aware of wild cards in the beginning of string, may not be valid? */ 1822 if (orig != null && orig.length() > 0) { 1823 orig = orig.replace("*", "%"); 1824 where = 1825 " AND " 1826 + BluetoothMapContract.MessageColumns.FROM_LIST 1827 + " LIKE '%" 1828 + orig 1829 + "%'"; 1830 } 1831 return where; 1832 } 1833 setWhereFilterOriginatorIM(BluetoothMapAppParams ap)1834 private static String setWhereFilterOriginatorIM(BluetoothMapAppParams ap) { 1835 String where = ""; 1836 String orig = ap.getFilterOriginator(); 1837 1838 /* Be aware of wild cards in the beginning of string, may not be valid? */ 1839 if (orig != null && orig.length() > 0) { 1840 orig = orig.replace("*", "%"); 1841 where = 1842 " AND " 1843 + BluetoothMapContract.MessageColumns.FROM_LIST 1844 + " LIKE '%" 1845 + orig 1846 + "%'"; 1847 } 1848 return where; 1849 } 1850 setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi)1851 private static String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) { 1852 String where = ""; 1853 int pri = ap.getFilterPriority(); 1854 /*only MMS have priority info */ 1855 if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1856 if (pri == 0x0002) { 1857 where = 1858 " AND " 1859 + Mms.PRIORITY 1860 + "<=" 1861 + Integer.toString(PduHeaders.PRIORITY_NORMAL); 1862 } else if (pri == 0x0001) { 1863 where = " AND " + Mms.PRIORITY + "=" + Integer.toString(PduHeaders.PRIORITY_HIGH); 1864 } 1865 } 1866 if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1867 if (pri == 0x0002) { 1868 where = " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "!=1"; 1869 } else if (pri == 0x0001) { 1870 where = " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "=1"; 1871 } 1872 } 1873 // TODO: no priority filtering in IM 1874 return where; 1875 } 1876 setWhereFilterRecipientEmail(BluetoothMapAppParams ap)1877 private static String setWhereFilterRecipientEmail(BluetoothMapAppParams ap) { 1878 String where = ""; 1879 String recipient = ap.getFilterRecipient(); 1880 1881 /* Be aware of wild cards in the beginning of string, may not be valid? */ 1882 if (recipient != null && recipient.length() > 0) { 1883 recipient = recipient.replace("*", "%"); 1884 where = 1885 " AND (" 1886 + BluetoothMapContract.MessageColumns.TO_LIST 1887 + " LIKE '%" 1888 + recipient 1889 + "%' OR " 1890 + BluetoothMapContract.MessageColumns.CC_LIST 1891 + " LIKE '%" 1892 + recipient 1893 + "%' OR " 1894 + BluetoothMapContract.MessageColumns.BCC_LIST 1895 + " LIKE '%" 1896 + recipient 1897 + "%' )"; 1898 } 1899 return where; 1900 } 1901 setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi)1902 private static String setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi) { 1903 String where = ""; 1904 long id = -1; 1905 String msgHandle = ap.getFilterMsgHandleString(); 1906 if (msgHandle != null) { 1907 id = BluetoothMapUtils.getCpHandle(msgHandle); 1908 Log.d(TAG, "id: " + id); 1909 } 1910 if (id != -1) { 1911 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1912 where = " AND " + Sms._ID + " = " + id; 1913 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1914 where = " AND " + Mms._ID + " = " + id; 1915 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1916 where = " AND " + BluetoothMapContract.MessageColumns._ID + " = " + id; 1917 } 1918 } 1919 return where; 1920 } 1921 setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi)1922 private static String setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi) { 1923 String where = ""; 1924 long id = -1; 1925 String msgHandle = ap.getFilterConvoIdString(); 1926 if (msgHandle != null) { 1927 id = BluetoothMapUtils.getMsgHandleAsLong(msgHandle); 1928 Log.d(TAG, "id: " + id); 1929 } 1930 if (id > 0) { 1931 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1932 where = " AND " + Sms.THREAD_ID + " = " + id; 1933 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1934 where = " AND " + Mms.THREAD_ID + " = " + id; 1935 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { 1936 where = " AND " + BluetoothMapContract.MessageColumns.THREAD_ID + " = " + id; 1937 } 1938 } 1939 1940 return where; 1941 } 1942 setWhereFilter( BluetoothMapFolderElement folderElement, FilterInfo fi, BluetoothMapAppParams ap)1943 private static String setWhereFilter( 1944 BluetoothMapFolderElement folderElement, FilterInfo fi, BluetoothMapAppParams ap) { 1945 StringBuilder where = new StringBuilder(setWhereFilterFolderType(folderElement, fi)); 1946 1947 String msgHandleWhere = setWhereFilterMessageHandle(ap, fi); 1948 /* if message handle filter is available, the other filters should be ignored */ 1949 if (!msgHandleWhere.isEmpty()) { 1950 where.append(msgHandleWhere); 1951 return where.toString(); 1952 } 1953 1954 where.append(setWhereFilterReadStatus(ap, fi)); 1955 where.append(setWhereFilterPriority(ap, fi)); 1956 where.append(setWhereFilterPeriod(ap, fi)); 1957 if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 1958 where.append(setWhereFilterOriginatorEmail(ap)); 1959 where.append(setWhereFilterRecipientEmail(ap)); 1960 } 1961 if (fi.mMsgType == FilterInfo.TYPE_IM) { 1962 where.append(setWhereFilterOriginatorIM(ap)); 1963 // TODO: set 'where' filer recipient? 1964 } 1965 where.append(setWhereFilterThreadId(ap, fi)); 1966 1967 return where.toString(); 1968 } 1969 1970 /* Used only for SMS/MMS */ 1971 @VisibleForTesting setConvoWhereFilterSmsMms( StringBuilder selection, FilterInfo fi, BluetoothMapAppParams ap)1972 void setConvoWhereFilterSmsMms( 1973 StringBuilder selection, FilterInfo fi, BluetoothMapAppParams ap) { 1974 1975 if (smsSelected(fi, ap) || mmsSelected(ap)) { 1976 1977 // Filter Read Status 1978 if (ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1979 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_UNREAD_ONLY) != 0) { 1980 selection.append(" AND ").append(Threads.READ).append(" = 0"); 1981 } 1982 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_READ_ONLY) != 0) { 1983 selection.append(" AND ").append(Threads.READ).append(" = 1"); 1984 } 1985 } 1986 1987 // Filter time 1988 if ((ap.getFilterLastActivityBegin() 1989 != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) { 1990 selection 1991 .append(" AND ") 1992 .append(Threads.DATE) 1993 .append(" >= ") 1994 .append(ap.getFilterLastActivityBegin()); 1995 } 1996 if ((ap.getFilterLastActivityEnd() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) { 1997 selection 1998 .append(" AND ") 1999 .append(Threads.DATE) 2000 .append(" <= ") 2001 .append(ap.getFilterLastActivityEnd()); 2002 } 2003 2004 // Filter ConvoId 2005 long convoId = -1; 2006 if (ap.getFilterConvoId() != null) { 2007 convoId = ap.getFilterConvoId().leastSignificantBits(); 2008 } 2009 if (convoId > 0) { 2010 selection 2011 .append(" AND ") 2012 .append(Threads._ID) 2013 .append(" = ") 2014 .append(Long.toString(convoId)); 2015 } 2016 } 2017 } 2018 2019 /** 2020 * Determine from application parameter if sms should be included. The filter mask is set for 2021 * message types not selected 2022 * 2023 * @return boolean true if sms is selected, false if not 2024 */ 2025 @VisibleForTesting smsSelected(FilterInfo fi, BluetoothMapAppParams ap)2026 boolean smsSelected(FilterInfo fi, BluetoothMapAppParams ap) { 2027 int msgType = ap.getFilterMessageType(); 2028 int phoneType = fi.mPhoneType; 2029 2030 Log.d(TAG, "smsSelected msgType: " + msgType); 2031 2032 if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 2033 return true; 2034 } 2035 2036 if ((msgType 2037 & (BluetoothMapAppParams.FILTER_NO_SMS_CDMA 2038 | BluetoothMapAppParams.FILTER_NO_SMS_GSM)) 2039 == 0) { 2040 return true; 2041 } 2042 2043 if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0) 2044 && (phoneType == TelephonyManager.PHONE_TYPE_GSM)) { 2045 return true; 2046 } 2047 2048 if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0) 2049 && (phoneType == TelephonyManager.PHONE_TYPE_CDMA)) { 2050 return true; 2051 } 2052 2053 return false; 2054 } 2055 2056 /** 2057 * Determine from application parameter if mms should be included. The filter mask is set for 2058 * message types not selected 2059 * 2060 * @return boolean true if mms is selected, false if not 2061 */ 2062 @VisibleForTesting mmsSelected(BluetoothMapAppParams ap)2063 boolean mmsSelected(BluetoothMapAppParams ap) { 2064 int msgType = ap.getFilterMessageType(); 2065 2066 Log.d(TAG, "mmsSelected msgType: " + msgType); 2067 2068 if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 2069 return true; 2070 } 2071 2072 if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0) { 2073 return true; 2074 } 2075 2076 return false; 2077 } 2078 2079 /** 2080 * Determine from application parameter if email should be included. The filter mask is set for 2081 * message types not selected 2082 * 2083 * @return boolean true if email is selected, false if not 2084 */ emailSelected(BluetoothMapAppParams ap)2085 private static boolean emailSelected(BluetoothMapAppParams ap) { 2086 int msgType = ap.getFilterMessageType(); 2087 2088 Log.d(TAG, "emailSelected msgType: " + msgType); 2089 2090 if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 2091 return true; 2092 } 2093 2094 if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0) { 2095 return true; 2096 } 2097 2098 return false; 2099 } 2100 2101 /** 2102 * Determine from application parameter if IM should be included. The filter mask is set for 2103 * message types not selected 2104 * 2105 * @return boolean true if im is selected, false if not 2106 */ imSelected(BluetoothMapAppParams ap)2107 private static boolean imSelected(BluetoothMapAppParams ap) { 2108 int msgType = ap.getFilterMessageType(); 2109 2110 Log.d(TAG, "imSelected msgType: " + msgType); 2111 2112 if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 2113 return true; 2114 } 2115 2116 if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0) { 2117 return true; 2118 } 2119 2120 return false; 2121 } 2122 2123 @VisibleForTesting setFilterInfo(FilterInfo fi)2124 void setFilterInfo(FilterInfo fi) { 2125 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 2126 if (tm != null) { 2127 fi.mPhoneType = tm.getPhoneType(); 2128 fi.mPhoneNum = tm.getLine1Number(); 2129 } 2130 } 2131 2132 /** 2133 * Get a listing of message in folder after applying filter. 2134 * 2135 * @param folderElement Must contain a valid folder string != null 2136 * @param ap Parameters specifying message content and filters 2137 * @return Listing object containing requested messages 2138 */ msgListing( BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2139 public BluetoothMapMessageListing msgListing( 2140 BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap) { 2141 Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType()); 2142 2143 BluetoothMapMessageListing bmList = new BluetoothMapMessageListing(); 2144 2145 /* We overwrite the parameter mask here if it is 0 or not present, as this 2146 * should cause all parameters to be included in the message list. */ 2147 if (ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER 2148 || ap.getParameterMask() == 0) { 2149 ap.setParameterMask(PARAMETER_MASK_ALL_ENABLED); 2150 Log.v( 2151 TAG, 2152 "msgListing(): appParameterMask is zero or not present, " 2153 + "changing to all enabled by default: " 2154 + ap.getParameterMask()); 2155 } 2156 Log.v( 2157 TAG, 2158 "folderElement hasSmsMmsContent = " 2159 + folderElement.hasSmsMmsContent() 2160 + " folderElement.hasEmailContent = " 2161 + folderElement.hasEmailContent() 2162 + " folderElement.hasImContent = " 2163 + folderElement.hasImContent()); 2164 2165 /* Cache some info used throughout filtering */ 2166 FilterInfo fi = new FilterInfo(); 2167 setFilterInfo(fi); 2168 Cursor smsCursor = null; 2169 Cursor mmsCursor = null; 2170 Cursor emailCursor = null; 2171 Cursor imCursor = null; 2172 String limit = ""; 2173 int offsetNum = ap.getStartOffset(); 2174 if (ap.getMaxListCount() > 0) { 2175 limit = " LIMIT " + (ap.getMaxListCount() + ap.getStartOffset()); 2176 } 2177 try { 2178 if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { 2179 if (ap.getFilterMessageType() 2180 == (BluetoothMapAppParams.FILTER_NO_EMAIL 2181 | BluetoothMapAppParams.FILTER_NO_MMS 2182 | BluetoothMapAppParams.FILTER_NO_SMS_GSM 2183 | BluetoothMapAppParams.FILTER_NO_IM) 2184 || ap.getFilterMessageType() 2185 == (BluetoothMapAppParams.FILTER_NO_EMAIL 2186 | BluetoothMapAppParams.FILTER_NO_MMS 2187 | BluetoothMapAppParams.FILTER_NO_SMS_CDMA 2188 | BluetoothMapAppParams.FILTER_NO_IM)) { 2189 // set real limit and offset if only this type is used 2190 // (only if offset/limit is used) 2191 limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); 2192 Log.d(TAG, "SMS Limit => " + limit); 2193 offsetNum = 0; 2194 } 2195 fi.mMsgType = FilterInfo.TYPE_SMS; 2196 if (ap.getFilterPriority() != 1) { 2197 /*SMS cannot have high priority*/ 2198 String where = setWhereFilter(folderElement, fi, ap); 2199 Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); 2200 smsCursor = 2201 BluetoothMethodProxy.getInstance() 2202 .contentResolverQuery( 2203 mResolver, 2204 Sms.CONTENT_URI, 2205 SMS_PROJECTION, 2206 where, 2207 null, 2208 Sms.DATE + " DESC" + limit); 2209 if (smsCursor != null) { 2210 BluetoothMapMessageListingElement e = null; 2211 // store column index so we dont have to look them up anymore (optimization) 2212 Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages."); 2213 fi.setSmsColumns(smsCursor); 2214 while (smsCursor.moveToNext()) { 2215 if (matchAddresses(smsCursor, fi, ap)) { 2216 BluetoothMapUtils.printCursor(smsCursor); 2217 e = element(smsCursor, fi, ap); 2218 bmList.add(e); 2219 } 2220 } 2221 } 2222 } 2223 } 2224 2225 if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { 2226 if (ap.getFilterMessageType() 2227 == (BluetoothMapAppParams.FILTER_NO_EMAIL 2228 | BluetoothMapAppParams.FILTER_NO_SMS_CDMA 2229 | BluetoothMapAppParams.FILTER_NO_SMS_GSM 2230 | BluetoothMapAppParams.FILTER_NO_IM)) { 2231 // set real limit and offset if only this type is used 2232 // (only if offset/limit is used) 2233 limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); 2234 Log.d(TAG, "MMS Limit => " + limit); 2235 offsetNum = 0; 2236 } 2237 fi.mMsgType = FilterInfo.TYPE_MMS; 2238 String where = 2239 setWhereFilter(folderElement, fi, ap) 2240 + " AND " 2241 + INTERESTED_MESSAGE_TYPE_CLAUSE; 2242 if (!where.isEmpty()) { 2243 Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); 2244 mmsCursor = 2245 BluetoothMethodProxy.getInstance() 2246 .contentResolverQuery( 2247 mResolver, 2248 Mms.CONTENT_URI, 2249 MMS_PROJECTION, 2250 where, 2251 null, 2252 Mms.DATE + " DESC" + limit); 2253 if (mmsCursor != null) { 2254 BluetoothMapMessageListingElement e = null; 2255 // store column index so we dont have to look them up anymore (optimization) 2256 fi.setMmsColumns(mmsCursor); 2257 Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages."); 2258 while (mmsCursor.moveToNext()) { 2259 if (matchAddresses(mmsCursor, fi, ap)) { 2260 BluetoothMapUtils.printCursor(mmsCursor); 2261 e = element(mmsCursor, fi, ap); 2262 bmList.add(e); 2263 } 2264 } 2265 } 2266 } 2267 } 2268 2269 if (emailSelected(ap) && folderElement.hasEmailContent()) { 2270 if (ap.getFilterMessageType() 2271 == (BluetoothMapAppParams.FILTER_NO_MMS 2272 | BluetoothMapAppParams.FILTER_NO_SMS_CDMA 2273 | BluetoothMapAppParams.FILTER_NO_SMS_GSM 2274 | BluetoothMapAppParams.FILTER_NO_IM)) { 2275 // set real limit and offset if only this type is used 2276 // (only if offset/limit is used) 2277 limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); 2278 Log.d(TAG, "Email Limit => " + limit); 2279 offsetNum = 0; 2280 } 2281 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2282 String where = setWhereFilter(folderElement, fi, ap); 2283 2284 if (!where.isEmpty()) { 2285 Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); 2286 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2287 emailCursor = 2288 BluetoothMethodProxy.getInstance() 2289 .contentResolverQuery( 2290 mResolver, 2291 contentUri, 2292 BluetoothMapContract.BT_MESSAGE_PROJECTION, 2293 where, 2294 null, 2295 BluetoothMapContract.MessageColumns.DATE 2296 + " DESC" 2297 + limit); 2298 if (emailCursor != null) { 2299 BluetoothMapMessageListingElement e = null; 2300 // store column index so we dont have to look them up anymore (optimization) 2301 fi.setEmailMessageColumns(emailCursor); 2302 Log.d(TAG, "Found " + emailCursor.getCount() + " email messages."); 2303 while (emailCursor.moveToNext()) { 2304 BluetoothMapUtils.printCursor(emailCursor); 2305 e = element(emailCursor, fi, ap); 2306 bmList.add(e); 2307 } 2308 // emailCursor.close(); 2309 } 2310 } 2311 } 2312 2313 if (imSelected(ap) && folderElement.hasImContent()) { 2314 if (ap.getFilterMessageType() 2315 == (BluetoothMapAppParams.FILTER_NO_MMS 2316 | BluetoothMapAppParams.FILTER_NO_SMS_CDMA 2317 | BluetoothMapAppParams.FILTER_NO_SMS_GSM 2318 | BluetoothMapAppParams.FILTER_NO_EMAIL)) { 2319 // set real limit and offset if only this type is used 2320 // (only if offset/limit is used) 2321 limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); 2322 Log.d(TAG, "IM Limit => " + limit); 2323 offsetNum = 0; 2324 } 2325 fi.mMsgType = FilterInfo.TYPE_IM; 2326 String where = setWhereFilter(folderElement, fi, ap); 2327 Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); 2328 2329 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2330 imCursor = 2331 BluetoothMethodProxy.getInstance() 2332 .contentResolverQuery( 2333 mResolver, 2334 contentUri, 2335 BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, 2336 where, 2337 null, 2338 BluetoothMapContract.MessageColumns.DATE + " DESC" + limit); 2339 if (imCursor != null) { 2340 BluetoothMapMessageListingElement e = null; 2341 // store column index so we dont have to look them up anymore (optimization) 2342 fi.setImMessageColumns(imCursor); 2343 Log.d(TAG, "Found " + imCursor.getCount() + " im messages."); 2344 while (imCursor.moveToNext()) { 2345 BluetoothMapUtils.printCursor(imCursor); 2346 e = element(imCursor, fi, ap); 2347 bmList.add(e); 2348 } 2349 } 2350 } 2351 2352 /* Enable this if post sorting and segmenting needed */ 2353 bmList.sort(); 2354 bmList.segment(ap.getMaxListCount(), offsetNum); 2355 List<BluetoothMapMessageListingElement> list = bmList.getList(); 2356 int listSize = list.size(); 2357 Cursor tmpCursor = null; 2358 for (int x = 0; x < listSize; x++) { 2359 BluetoothMapMessageListingElement ele = list.get(x); 2360 /* If OBEX "GET" request header includes "ParameterMask" with 'Type' NOT set, 2361 * then ele.getType() returns "null" even for a valid cursor. 2362 * Avoid NullPointerException in equals() check when 'mType' value is "null" */ 2363 TYPE tmpType = ele.getType(); 2364 if (smsCursor != null 2365 && ((TYPE.SMS_GSM).equals(tmpType) || (TYPE.SMS_CDMA).equals(tmpType))) { 2366 tmpCursor = smsCursor; 2367 fi.mMsgType = FilterInfo.TYPE_SMS; 2368 } else if (mmsCursor != null && (TYPE.MMS).equals(tmpType)) { 2369 tmpCursor = mmsCursor; 2370 fi.mMsgType = FilterInfo.TYPE_MMS; 2371 } else if (emailCursor != null && ((TYPE.EMAIL).equals(tmpType))) { 2372 tmpCursor = emailCursor; 2373 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2374 } else if (imCursor != null && ((TYPE.IM).equals(tmpType))) { 2375 tmpCursor = imCursor; 2376 fi.mMsgType = FilterInfo.TYPE_IM; 2377 } 2378 if (tmpCursor != null) { 2379 tmpCursor.moveToPosition(ele.getCursorIndex()); 2380 setSenderAddressing(ele, tmpCursor, fi, ap); 2381 setSenderName(ele, tmpCursor, fi, ap); 2382 setRecipientAddressing(ele, tmpCursor, fi, ap); 2383 setRecipientName(ele, tmpCursor, fi, ap); 2384 setSubject(ele, tmpCursor, fi, ap); 2385 setSize(ele, tmpCursor, fi, ap); 2386 setText(ele, tmpCursor, fi, ap); 2387 setPriority(ele, tmpCursor, fi, ap); 2388 setSent(ele, tmpCursor, fi, ap); 2389 setProtected(ele, tmpCursor, fi, ap); 2390 setReceptionStatus(ele, ap); 2391 setAttachment(ele, tmpCursor, fi, ap); 2392 2393 if (mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10) { 2394 setDeliveryStatus(ele, tmpCursor, fi, ap); 2395 setThreadId(ele, tmpCursor, fi, ap); 2396 setThreadName(ele, tmpCursor, fi, ap); 2397 } 2398 } 2399 } 2400 } finally { 2401 if (emailCursor != null) { 2402 emailCursor.close(); 2403 } 2404 if (smsCursor != null) { 2405 smsCursor.close(); 2406 } 2407 if (mmsCursor != null) { 2408 mmsCursor.close(); 2409 } 2410 if (imCursor != null) { 2411 imCursor.close(); 2412 } 2413 } 2414 2415 Log.d(TAG, "msgListing end"); 2416 return bmList; 2417 } 2418 2419 /** 2420 * Get the size of the message listing 2421 * 2422 * @param folderElement Must contain a valid folder string != null 2423 * @param ap Parameters specifying message content and filters 2424 * @return Integer equal to message listing size 2425 */ msgListingSize(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2426 public int msgListingSize(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap) { 2427 Log.d(TAG, "msgListingSize: folder = " + folderElement.getName()); 2428 int cnt = 0; 2429 2430 /* Cache some info used throughout filtering */ 2431 FilterInfo fi = new FilterInfo(); 2432 setFilterInfo(fi); 2433 2434 if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { 2435 fi.mMsgType = FilterInfo.TYPE_SMS; 2436 String where = setWhereFilter(folderElement, fi, ap); 2437 Cursor c = 2438 BluetoothMethodProxy.getInstance() 2439 .contentResolverQuery( 2440 mResolver, 2441 Sms.CONTENT_URI, 2442 SMS_PROJECTION, 2443 where, 2444 null, 2445 Sms.DATE + " DESC"); 2446 try { 2447 if (c != null) { 2448 cnt = c.getCount(); 2449 } 2450 } finally { 2451 if (c != null) { 2452 c.close(); 2453 } 2454 } 2455 } 2456 2457 if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { 2458 fi.mMsgType = FilterInfo.TYPE_MMS; 2459 String where = setWhereFilter(folderElement, fi, ap); 2460 Cursor c = 2461 BluetoothMethodProxy.getInstance() 2462 .contentResolverQuery( 2463 mResolver, 2464 Mms.CONTENT_URI, 2465 MMS_PROJECTION, 2466 where, 2467 null, 2468 Mms.DATE + " DESC"); 2469 try { 2470 if (c != null) { 2471 cnt += c.getCount(); 2472 } 2473 } finally { 2474 if (c != null) { 2475 c.close(); 2476 } 2477 } 2478 } 2479 2480 if (emailSelected(ap) && folderElement.hasEmailContent()) { 2481 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2482 String where = setWhereFilter(folderElement, fi, ap); 2483 if (!where.isEmpty()) { 2484 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2485 Cursor c = 2486 BluetoothMethodProxy.getInstance() 2487 .contentResolverQuery( 2488 mResolver, 2489 contentUri, 2490 BluetoothMapContract.BT_MESSAGE_PROJECTION, 2491 where, 2492 null, 2493 BluetoothMapContract.MessageColumns.DATE + " DESC"); 2494 try { 2495 if (c != null) { 2496 cnt += c.getCount(); 2497 } 2498 } finally { 2499 if (c != null) { 2500 c.close(); 2501 } 2502 } 2503 } 2504 } 2505 2506 if (imSelected(ap) && folderElement.hasImContent()) { 2507 fi.mMsgType = FilterInfo.TYPE_IM; 2508 String where = setWhereFilter(folderElement, fi, ap); 2509 if (!where.isEmpty()) { 2510 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2511 Cursor c = 2512 BluetoothMethodProxy.getInstance() 2513 .contentResolverQuery( 2514 mResolver, 2515 contentUri, 2516 BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, 2517 where, 2518 null, 2519 BluetoothMapContract.MessageColumns.DATE + " DESC"); 2520 try { 2521 if (c != null) { 2522 cnt += c.getCount(); 2523 } 2524 } finally { 2525 if (c != null) { 2526 c.close(); 2527 } 2528 } 2529 } 2530 } 2531 2532 Log.d(TAG, "msgListingSize: size = " + cnt); 2533 return cnt; 2534 } 2535 2536 /** 2537 * Return true if there are unread messages in the requested list of messages 2538 * 2539 * @param folderElement folder where the message listing should come from 2540 * @param ap application parameter object 2541 * @return true if unread messages are in the list, else false 2542 */ msgListingHasUnread( BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2543 public boolean msgListingHasUnread( 2544 BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap) { 2545 Log.d(TAG, "msgListingHasUnread: folder = " + folderElement.getName()); 2546 int cnt = 0; 2547 2548 /* Cache some info used throughout filtering */ 2549 FilterInfo fi = new FilterInfo(); 2550 setFilterInfo(fi); 2551 2552 if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { 2553 fi.mMsgType = FilterInfo.TYPE_SMS; 2554 String where = 2555 setWhereFilterFolderType(folderElement, fi) 2556 + " AND " 2557 + Sms.READ 2558 + "=0 " 2559 + setWhereFilterPeriod(ap, fi); 2560 Cursor c = 2561 BluetoothMethodProxy.getInstance() 2562 .contentResolverQuery( 2563 mResolver, 2564 Sms.CONTENT_URI, 2565 SMS_PROJECTION, 2566 where, 2567 null, 2568 Sms.DATE + " DESC"); 2569 try { 2570 if (c != null) { 2571 cnt = c.getCount(); 2572 } 2573 } finally { 2574 if (c != null) { 2575 c.close(); 2576 } 2577 } 2578 } 2579 2580 if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { 2581 fi.mMsgType = FilterInfo.TYPE_MMS; 2582 String where = 2583 setWhereFilterFolderType(folderElement, fi) 2584 + " AND " 2585 + Mms.READ 2586 + "=0 " 2587 + setWhereFilterPeriod(ap, fi); 2588 Cursor c = 2589 BluetoothMethodProxy.getInstance() 2590 .contentResolverQuery( 2591 mResolver, 2592 Mms.CONTENT_URI, 2593 MMS_PROJECTION, 2594 where, 2595 null, 2596 Sms.DATE + " DESC"); 2597 try { 2598 if (c != null) { 2599 cnt += c.getCount(); 2600 } 2601 } finally { 2602 if (c != null) { 2603 c.close(); 2604 } 2605 } 2606 } 2607 2608 if (emailSelected(ap) && folderElement.getFolderId() != -1) { 2609 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2610 String where = setWhereFilterFolderType(folderElement, fi); 2611 if (!where.isEmpty()) { 2612 where = 2613 where 2614 + " AND " 2615 + BluetoothMapContract.MessageColumns.FLAG_READ 2616 + "=0 " 2617 + setWhereFilterPeriod(ap, fi); 2618 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2619 Cursor c = 2620 BluetoothMethodProxy.getInstance() 2621 .contentResolverQuery( 2622 mResolver, 2623 contentUri, 2624 BluetoothMapContract.BT_MESSAGE_PROJECTION, 2625 where, 2626 null, 2627 BluetoothMapContract.MessageColumns.DATE + " DESC"); 2628 try { 2629 if (c != null) { 2630 cnt += c.getCount(); 2631 } 2632 } finally { 2633 if (c != null) { 2634 c.close(); 2635 } 2636 } 2637 } 2638 } 2639 2640 if (imSelected(ap) && folderElement.hasImContent()) { 2641 fi.mMsgType = FilterInfo.TYPE_IM; 2642 String where = setWhereFilter(folderElement, fi, ap); 2643 if (!where.isEmpty()) { 2644 where = 2645 where 2646 + " AND " 2647 + BluetoothMapContract.MessageColumns.FLAG_READ 2648 + "=0 " 2649 + setWhereFilterPeriod(ap, fi); 2650 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2651 Cursor c = 2652 BluetoothMethodProxy.getInstance() 2653 .contentResolverQuery( 2654 mResolver, 2655 contentUri, 2656 BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, 2657 where, 2658 null, 2659 BluetoothMapContract.MessageColumns.DATE + " DESC"); 2660 try { 2661 if (c != null) { 2662 cnt += c.getCount(); 2663 } 2664 } finally { 2665 if (c != null) { 2666 c.close(); 2667 } 2668 } 2669 } 2670 } 2671 2672 Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt); 2673 return cnt > 0; 2674 } 2675 2676 /** 2677 * Build the conversation listing. 2678 * 2679 * @param ap The Application Parameters 2680 * @param sizeOnly TRUE: don't populate the list members, only build the list to get the size. 2681 * @return the BluetoothMapConvoListing 2682 */ convoListing(BluetoothMapAppParams ap, boolean sizeOnly)2683 BluetoothMapConvoListing convoListing(BluetoothMapAppParams ap, boolean sizeOnly) { 2684 2685 Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType()); 2686 BluetoothMapConvoListing convoList = new BluetoothMapConvoListing(); 2687 2688 /* We overwrite the parameter mask here if it is 0 or not present, as this 2689 * should cause all parameters to be included in the message list. */ 2690 if (ap.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER 2691 || ap.getConvoParameterMask() == 0) { 2692 ap.setConvoParameterMask(CONVO_PARAMETER_MASK_DEFAULT); 2693 Log.d( 2694 TAG, 2695 "convoListing(): appParameterMask is zero or not present, " 2696 + "changing to default: " 2697 + ap.getConvoParameterMask()); 2698 } 2699 2700 /* Possible filters: 2701 * - Recipient name (contacts DB) or id (for SMS/MMS this is the thread-id contact-id) 2702 * - Activity start/begin 2703 * - Read status 2704 * - Thread_id 2705 * The strategy for SMS/MMS 2706 * With no filter on name - use limit and offset. 2707 * With a filter on name - build the complete list of conversations and create a filter 2708 * mechanism 2709 * 2710 * The strategy for IM: 2711 * Join the conversation table with the contacts table in a way that makes it possible to 2712 * get the data needed in a single query. 2713 * Manually handle limit/offset 2714 * */ 2715 2716 /* Cache some info used throughout filtering */ 2717 FilterInfo fi = new FilterInfo(); 2718 setFilterInfo(fi); 2719 Cursor smsMmsCursor = null; 2720 Cursor imEmailCursor = null; 2721 int offsetNum; 2722 if (sizeOnly) { 2723 offsetNum = 0; 2724 } else { 2725 offsetNum = ap.getStartOffset(); 2726 } 2727 // Inverse meaning - hence a 1 is include. 2728 int msgTypesInclude = 2729 ((~ap.getFilterMessageType()) & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK); 2730 int maxThreads = ap.getMaxListCount() + ap.getStartOffset(); 2731 2732 try { 2733 if (smsSelected(fi, ap) || mmsSelected(ap)) { 2734 String limit = ""; 2735 if ((!sizeOnly) 2736 && (ap.getMaxListCount() > 0) 2737 && (ap.getFilterRecipient() == null)) { 2738 /* We can only use limit if we do not have a contacts filter */ 2739 limit = " LIMIT " + maxThreads; 2740 } 2741 StringBuilder sortOrder = new StringBuilder(Threads.DATE + " DESC"); 2742 if ((!sizeOnly) 2743 && (((msgTypesInclude 2744 & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM 2745 | BluetoothMapAppParams.FILTER_NO_SMS_CDMA)) 2746 | BluetoothMapAppParams.FILTER_NO_MMS) 2747 == 0) 2748 && ap.getFilterRecipient() == null) { 2749 // SMS/MMS messages only and no recipient filter - use optimization. 2750 limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); 2751 Log.d(TAG, "SMS Limit => " + limit); 2752 offsetNum = 0; 2753 } 2754 StringBuilder selection = new StringBuilder(120); // This covers most cases 2755 selection.append("1=1 "); // just to simplify building the where-clause 2756 setConvoWhereFilterSmsMms(selection, fi, ap); 2757 Uri uri = 2758 Threads.CONTENT_URI 2759 .buildUpon() 2760 .appendQueryParameter("simple", "true") 2761 .build(); 2762 sortOrder.append(limit); 2763 Log.d( 2764 TAG, 2765 "Query using selection: " 2766 + selection.toString() 2767 + " - sortOrder: " 2768 + sortOrder.toString()); 2769 // TODO: Optimize: Reduce projection based on convo parameter mask 2770 smsMmsCursor = 2771 BluetoothMethodProxy.getInstance() 2772 .contentResolverQuery( 2773 mResolver, 2774 uri, 2775 MMS_SMS_THREAD_PROJECTION, 2776 selection.toString(), 2777 null, 2778 sortOrder.toString()); 2779 if (smsMmsCursor != null) { 2780 // store column index so we don't have to look them up anymore (optimization) 2781 Log.d(TAG, "Found " + smsMmsCursor.getCount() + " sms/mms conversations."); 2782 BluetoothMapConvoListingElement convoElement = null; 2783 smsMmsCursor.moveToPosition(-1); 2784 if (ap.getFilterRecipient() == null) { 2785 int count = 0; 2786 // We have no Recipient filter, add contacts after the list is reduced 2787 while (smsMmsCursor.moveToNext()) { 2788 convoElement = createConvoElement(smsMmsCursor, fi); 2789 convoList.add(convoElement); 2790 count++; 2791 if (!sizeOnly && count >= maxThreads) { 2792 break; 2793 } 2794 } 2795 } else { 2796 // We must be able to filter on recipient, add contacts now 2797 SmsMmsContacts contacts = new SmsMmsContacts(); 2798 while (smsMmsCursor.moveToNext()) { 2799 int count = 0; 2800 convoElement = createConvoElement(smsMmsCursor, fi); 2801 String idsStr = 2802 smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); 2803 // Add elements only if we do find a contact - if not we cannot apply 2804 // the filter, hence the item is irrelevant 2805 // TODO: Perhaps the spec. should be changes to be able to search on 2806 // phone number as well? 2807 if (addSmsMmsContacts( 2808 convoElement, contacts, idsStr, ap.getFilterRecipient(), ap)) { 2809 convoList.add(convoElement); 2810 if (!sizeOnly && count >= maxThreads) { 2811 break; 2812 } 2813 } 2814 } 2815 } 2816 } 2817 } 2818 2819 if (emailSelected(ap) || imSelected(ap)) { 2820 int count = 0; 2821 if (emailSelected(ap)) { 2822 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2823 } else if (imSelected(ap)) { 2824 fi.mMsgType = FilterInfo.TYPE_IM; 2825 } 2826 Log.d(TAG, "msgType: " + fi.mMsgType); 2827 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION); 2828 2829 contentUri = appendConvoListQueryParameters(ap, contentUri); 2830 Log.v(TAG, "URI with parameters: " + contentUri.toString()); 2831 // TODO: Optimize: Reduce projection based on convo parameter mask 2832 imEmailCursor = 2833 BluetoothMethodProxy.getInstance() 2834 .contentResolverQuery( 2835 mResolver, 2836 contentUri, 2837 BluetoothMapContract.BT_CONVERSATION_PROJECTION, 2838 null, 2839 null, 2840 BluetoothMapContract.ConversationColumns 2841 .LAST_THREAD_ACTIVITY 2842 + " DESC, " 2843 + BluetoothMapContract.ConversationColumns.THREAD_ID 2844 + " ASC"); 2845 if (imEmailCursor != null) { 2846 BluetoothMapConvoListingElement e = null; 2847 // store column index so we don't have to look them up anymore (optimization) 2848 // Here we rely on only a single account-based message type for each MAS. 2849 fi.setEmailImConvoColumns(imEmailCursor); 2850 boolean isValid = imEmailCursor.moveToNext(); 2851 Log.d( 2852 TAG, 2853 "Found " 2854 + imEmailCursor.getCount() 2855 + " EMAIL/IM conversations. isValid = " 2856 + isValid); 2857 while (isValid && ((sizeOnly) || (count < maxThreads))) { 2858 long threadId = imEmailCursor.getLong(fi.mConvoColConvoId); 2859 long nextThreadId; 2860 count++; 2861 e = createConvoElement(imEmailCursor, fi); 2862 convoList.add(e); 2863 2864 do { 2865 nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId); 2866 Log.v( 2867 TAG, 2868 " threadId = " + threadId + " newThreadId = " + nextThreadId); 2869 // TODO: This seems rather inefficient in the case where we do not need 2870 // to reduce the list. 2871 } while ((nextThreadId == threadId) 2872 && (isValid = imEmailCursor.moveToNext())); 2873 } 2874 } 2875 } 2876 2877 Log.d(TAG, "Done adding conversations - list size:" + convoList.getCount()); 2878 2879 // If sizeOnly - we are all done here - return the list as is - no need to populate the 2880 // list. 2881 if (sizeOnly) { 2882 return convoList; 2883 } 2884 2885 /* Enable this if post sorting and segmenting needed */ 2886 /* This is too early */ 2887 convoList.sort(); 2888 convoList.segment(ap.getMaxListCount(), offsetNum); 2889 List<BluetoothMapConvoListingElement> list = convoList.getList(); 2890 int listSize = list.size(); 2891 Log.v(TAG, "List Size:" + listSize); 2892 Cursor tmpCursor = null; 2893 SmsMmsContacts contacts = new SmsMmsContacts(); 2894 for (int x = 0; x < listSize; x++) { 2895 BluetoothMapConvoListingElement ele = list.get(x); 2896 TYPE type = ele.getType(); 2897 switch (type) { 2898 case SMS_CDMA: 2899 case SMS_GSM: 2900 case MMS: 2901 { 2902 tmpCursor = null; // SMS/MMS needs special treatment 2903 if (smsMmsCursor != null) { 2904 populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts); 2905 } 2906 break; 2907 } 2908 case EMAIL: 2909 tmpCursor = imEmailCursor; 2910 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2911 break; 2912 case IM: 2913 tmpCursor = imEmailCursor; 2914 fi.mMsgType = FilterInfo.TYPE_IM; 2915 break; 2916 default: 2917 tmpCursor = null; 2918 break; 2919 } 2920 2921 Log.d(TAG, "Working on cursor of type " + fi.mMsgType); 2922 2923 if (tmpCursor != null) { 2924 populateImEmailConvoElement(ele, tmpCursor, ap, fi); 2925 } else { 2926 // No, it will be for SMS/MMS at the moment 2927 Log.d( 2928 TAG, 2929 "tmpCursor is Null - something is wrong - or the message is" 2930 + " of type SMS/MMS"); 2931 } 2932 } 2933 } finally { 2934 if (imEmailCursor != null) { 2935 imEmailCursor.close(); 2936 } 2937 if (smsMmsCursor != null) { 2938 smsMmsCursor.close(); 2939 } 2940 Log.d(TAG, "conversation end"); 2941 } 2942 return convoList; 2943 } 2944 2945 /** 2946 * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a new 2947 * ConvoListVersionCounter in mSmsMmsConvoListVersion 2948 * 2949 * @return true if a list change has been detected 2950 */ refreshSmsMmsConvoVersions()2951 boolean refreshSmsMmsConvoVersions() { 2952 boolean listChangeDetected = false; 2953 Uri uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); 2954 Cursor cursor = 2955 mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null, null, Threads.DATE + " DESC"); 2956 try { 2957 if (cursor != null) { 2958 // store column index so we don't have to look them up anymore (optimization) 2959 Log.d(TAG, "Found " + cursor.getCount() + " sms/mms conversations."); 2960 BluetoothMapConvoListingElement convoElement = null; 2961 cursor.moveToPosition(-1); 2962 synchronized (getSmsMmsConvoList()) { 2963 int size = Math.max(getSmsMmsConvoList().size(), cursor.getCount()); 2964 Map<Long, BluetoothMapConvoListingElement> newList = 2965 new HashMap<Long, BluetoothMapConvoListingElement>(size); 2966 while (cursor.moveToNext()) { 2967 // TODO: Extract to function, that can be called at listing, which returns 2968 // the versionCounter(existing or new). 2969 boolean convoChanged = false; 2970 Long id = cursor.getLong(MMS_SMS_THREAD_COL_ID); 2971 convoElement = getSmsMmsConvoList().remove(id); 2972 if (convoElement == null) { 2973 // New conversation added 2974 convoElement = new BluetoothMapConvoListingElement(); 2975 convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id); 2976 listChangeDetected = true; 2977 convoElement.setVersionCounter(0); 2978 } 2979 // Currently we only need to compare name, lastActivity and read_status, and 2980 // name is not used for SMS/MMS. 2981 // msg delete will be handled by update folderVersionCounter(). 2982 long lastActivity = cursor.getLong(MMS_SMS_THREAD_COL_DATE); 2983 boolean read = cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1; 2984 2985 if (lastActivity != convoElement.getLastActivity()) { 2986 convoChanged = true; 2987 convoElement.setLastActivity(lastActivity); 2988 } 2989 2990 if (read != convoElement.getReadBool()) { 2991 convoChanged = true; 2992 convoElement.setRead(read, false); 2993 } 2994 2995 String idsStr = cursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); 2996 if (!idsStr.equals(convoElement.getSmsMmsContacts())) { 2997 // This should not trigger a change in conversationVersionCounter 2998 // only the 2999 // ConvoListVersionCounter. 3000 listChangeDetected = true; 3001 convoElement.setSmsMmsContacts(idsStr); 3002 } 3003 3004 if (convoChanged) { 3005 listChangeDetected = true; 3006 convoElement.incrementVersionCounter(); 3007 } 3008 newList.put(id, convoElement); 3009 } 3010 // If we still have items on the old list, something was deleted 3011 if (getSmsMmsConvoList().size() != 0) { 3012 listChangeDetected = true; 3013 } 3014 setSmsMmsConvoList(newList); 3015 } 3016 3017 if (listChangeDetected) { 3018 mMasInstance.updateSmsMmsConvoListVersionCounter(); 3019 } 3020 } 3021 } finally { 3022 if (cursor != null) { 3023 cursor.close(); 3024 } 3025 } 3026 return listChangeDetected; 3027 } 3028 3029 /** 3030 * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a new 3031 * ConvoListVersionCounter in mSmsMmsConvoListVersion 3032 * 3033 * @return true if a list change has been detected 3034 */ refreshImEmailConvoVersions()3035 boolean refreshImEmailConvoVersions() { 3036 boolean listChangeDetected = false; 3037 FilterInfo fi = new FilterInfo(); 3038 3039 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION); 3040 3041 Log.v(TAG, "URI with parameters: " + contentUri.toString()); 3042 Cursor imEmailCursor = 3043 mResolver.query( 3044 contentUri, 3045 CONVO_VERSION_PROJECTION, 3046 null, 3047 null, 3048 BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY 3049 + " DESC, " 3050 + BluetoothMapContract.ConversationColumns.THREAD_ID 3051 + " ASC"); 3052 try { 3053 if (imEmailCursor != null) { 3054 BluetoothMapConvoListingElement convoElement = null; 3055 // store column index so we don't have to look them up anymore (optimization) 3056 // Here we rely on only a single account-based message type for each MAS. 3057 fi.setEmailImConvoColumns(imEmailCursor); 3058 boolean isValid = imEmailCursor.moveToNext(); 3059 Log.v( 3060 TAG, 3061 "Found " 3062 + imEmailCursor.getCount() 3063 + " EMAIL/IM conversations. isValid = " 3064 + isValid); 3065 synchronized (getImEmailConvoList()) { 3066 int size = Math.max(getImEmailConvoList().size(), imEmailCursor.getCount()); 3067 boolean convoChanged = false; 3068 Map<Long, BluetoothMapConvoListingElement> newList = 3069 new HashMap<Long, BluetoothMapConvoListingElement>(size); 3070 while (isValid) { 3071 long id = imEmailCursor.getLong(fi.mConvoColConvoId); 3072 long nextThreadId; 3073 convoElement = getImEmailConvoList().remove(id); 3074 if (convoElement == null) { 3075 // New conversation added 3076 convoElement = new BluetoothMapConvoListingElement(); 3077 convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id); 3078 listChangeDetected = true; 3079 convoElement.setVersionCounter(0); 3080 } 3081 String name = imEmailCursor.getString(fi.mConvoColName); 3082 String summary = imEmailCursor.getString(fi.mConvoColSummary); 3083 long lastActivity = imEmailCursor.getLong(fi.mConvoColLastActivity); 3084 boolean read = imEmailCursor.getInt(fi.mConvoColRead) == 1; 3085 3086 if (lastActivity != convoElement.getLastActivity()) { 3087 convoChanged = true; 3088 convoElement.setLastActivity(lastActivity); 3089 } 3090 3091 if (read != convoElement.getReadBool()) { 3092 convoChanged = true; 3093 convoElement.setRead(read, false); 3094 } 3095 3096 if (name != null && !name.equals(convoElement.getName())) { 3097 convoChanged = true; 3098 convoElement.setName(name); 3099 } 3100 3101 if (summary != null && !summary.equals(convoElement.getFullSummary())) { 3102 convoChanged = true; 3103 convoElement.setSummary(summary); 3104 } 3105 /* If the query returned one row for each contact, skip all the 3106 duplicates */ 3107 do { 3108 nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId); 3109 Log.v(TAG, " threadId = " + id + " newThreadId = " + nextThreadId); 3110 } while ((nextThreadId == id) && (isValid = imEmailCursor.moveToNext())); 3111 3112 if (convoChanged) { 3113 listChangeDetected = true; 3114 convoElement.incrementVersionCounter(); 3115 } 3116 newList.put(id, convoElement); 3117 } 3118 // If we still have items on the old list, something was deleted 3119 if (getImEmailConvoList().size() != 0) { 3120 listChangeDetected = true; 3121 } 3122 setImEmailConvoList(newList); 3123 } 3124 } 3125 } finally { 3126 if (imEmailCursor != null) { 3127 imEmailCursor.close(); 3128 } 3129 } 3130 3131 if (listChangeDetected) { 3132 mMasInstance.updateImEmailConvoListVersionCounter(); 3133 } 3134 return listChangeDetected; 3135 } 3136 3137 /** 3138 * Update the convoVersionCounter within the element passed as parameter. This function has the 3139 * side effect to update the ConvoListVersionCounter if needed. This function ignores changes to 3140 * contacts as this shall not change the convoVersionCounter, only the convoListVersion counter, 3141 * which will be updated upon request. 3142 * 3143 * @param ele Element to update shall not be null. 3144 */ updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele)3145 private void updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele) { 3146 long id = ele.getCpConvoId(); 3147 BluetoothMapConvoListingElement convoElement = getSmsMmsConvoList().get(id); 3148 boolean listChangeDetected = false; 3149 boolean convoChanged = false; 3150 if (convoElement == null) { 3151 // New conversation added 3152 convoElement = new BluetoothMapConvoListingElement(); 3153 getSmsMmsConvoList().put(id, convoElement); 3154 convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id); 3155 listChangeDetected = true; 3156 convoElement.setVersionCounter(0); 3157 } 3158 long lastActivity = cursor.getLong(MMS_SMS_THREAD_COL_DATE); 3159 boolean read = cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1; 3160 3161 if (lastActivity != convoElement.getLastActivity()) { 3162 convoChanged = true; 3163 convoElement.setLastActivity(lastActivity); 3164 } 3165 3166 if (read != convoElement.getReadBool()) { 3167 convoChanged = true; 3168 convoElement.setRead(read, false); 3169 } 3170 3171 if (convoChanged) { 3172 listChangeDetected = true; 3173 convoElement.incrementVersionCounter(); 3174 } 3175 if (listChangeDetected) { 3176 mMasInstance.updateSmsMmsConvoListVersionCounter(); 3177 } 3178 ele.setVersionCounter(convoElement.getVersionCounter()); 3179 } 3180 3181 /** 3182 * Update the convoVersionCounter within the element passed as parameter. This function has the 3183 * side effect to update the ConvoListVersionCounter if needed. This function ignores changes to 3184 * contacts as this shall not change the convoVersionCounter, only the convoListVersion counter, 3185 * which will be updated upon request. 3186 * 3187 * @param ele Element to update shall not be null. 3188 */ updateImEmailConvoVersion( Cursor cursor, FilterInfo fi, BluetoothMapConvoListingElement ele)3189 private void updateImEmailConvoVersion( 3190 Cursor cursor, FilterInfo fi, BluetoothMapConvoListingElement ele) { 3191 long id = ele.getCpConvoId(); 3192 BluetoothMapConvoListingElement convoElement = getImEmailConvoList().get(id); 3193 boolean listChangeDetected = false; 3194 boolean convoChanged = false; 3195 if (convoElement == null) { 3196 // New conversation added 3197 Log.v(TAG, "Added new conversation with ID = " + id); 3198 convoElement = new BluetoothMapConvoListingElement(); 3199 convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id); 3200 getImEmailConvoList().put(id, convoElement); 3201 listChangeDetected = true; 3202 convoElement.setVersionCounter(0); 3203 } 3204 String name = cursor.getString(fi.mConvoColName); 3205 long lastActivity = cursor.getLong(fi.mConvoColLastActivity); 3206 boolean read = cursor.getInt(fi.mConvoColRead) == 1; 3207 3208 if (lastActivity != convoElement.getLastActivity()) { 3209 convoChanged = true; 3210 convoElement.setLastActivity(lastActivity); 3211 } 3212 3213 if (read != convoElement.getReadBool()) { 3214 convoChanged = true; 3215 convoElement.setRead(read, false); 3216 } 3217 3218 if (name != null && !name.equals(convoElement.getName())) { 3219 convoChanged = true; 3220 convoElement.setName(name); 3221 } 3222 3223 if (convoChanged) { 3224 listChangeDetected = true; 3225 Log.v(TAG, "conversation with ID = " + id + " changed"); 3226 convoElement.incrementVersionCounter(); 3227 } 3228 if (listChangeDetected) { 3229 mMasInstance.updateImEmailConvoListVersionCounter(); 3230 } 3231 ele.setVersionCounter(convoElement.getVersionCounter()); 3232 } 3233 populateSmsMmsConvoElement( BluetoothMapConvoListingElement ele, Cursor smsMmsCursor, BluetoothMapAppParams ap, SmsMmsContacts contacts)3234 private void populateSmsMmsConvoElement( 3235 BluetoothMapConvoListingElement ele, 3236 Cursor smsMmsCursor, 3237 BluetoothMapAppParams ap, 3238 SmsMmsContacts contacts) { 3239 smsMmsCursor.moveToPosition(ele.getCursorIndex()); 3240 // TODO: If we ever get beyond 31 bit, change to long 3241 int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value 3242 3243 // TODO: How to determine whether the convo-IDs can be used across message 3244 // types? 3245 ele.setConvoId( 3246 BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, 3247 smsMmsCursor.getLong(MMS_SMS_THREAD_COL_ID)); 3248 3249 boolean read = smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1; 3250 if ((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) { 3251 ele.setRead(read, true); 3252 } else { 3253 ele.setRead(read, false); 3254 } 3255 3256 if ((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) { 3257 long timeStamp = smsMmsCursor.getLong(MMS_SMS_THREAD_COL_DATE); 3258 ele.setLastActivity(timeStamp); 3259 } else { 3260 // We need to delete the time stamp, if it was added for multi msg-type 3261 ele.setLastActivity(-1); 3262 } 3263 3264 if ((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) { 3265 updateSmsMmsConvoVersion(smsMmsCursor, ele); 3266 } 3267 3268 if ((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) { 3269 ele.setName(""); // We never have a thread name for SMS/MMS 3270 } 3271 3272 if ((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) { 3273 String summary = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET); 3274 String cs = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET_CS); 3275 if (summary != null && cs != null && !cs.equals("UTF-8")) { 3276 try { 3277 // TODO: Not sure this is how to convert to UTF-8 3278 summary = new String(summary.getBytes(cs), StandardCharsets.UTF_8); 3279 } catch (UnsupportedEncodingException e) { 3280 ContentProfileErrorReportUtils.report( 3281 BluetoothProfile.MAP, 3282 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 3283 BluetoothStatsLog 3284 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 3285 3); 3286 Log.e(TAG, "populateSmsMmsConvoElement: " + e); 3287 } 3288 } 3289 ele.setSummary(summary); 3290 } 3291 3292 if ((parameterMask & CONVO_PARAM_MASK_PARTICIPANTS) != 0) { 3293 if (ap.getFilterRecipient() == null) { 3294 // Add contacts only if not already added 3295 String idsStr = smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); 3296 addSmsMmsContacts(ele, contacts, idsStr, null, ap); 3297 } 3298 } 3299 } 3300 populateImEmailConvoElement( BluetoothMapConvoListingElement ele, Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi)3301 private void populateImEmailConvoElement( 3302 BluetoothMapConvoListingElement ele, 3303 Cursor tmpCursor, 3304 BluetoothMapAppParams ap, 3305 FilterInfo fi) { 3306 tmpCursor.moveToPosition(ele.getCursorIndex()); 3307 // TODO: If we ever get beyond 31 bit, change to long 3308 int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value 3309 long threadId = tmpCursor.getLong(fi.mConvoColConvoId); 3310 3311 // Mandatory field 3312 ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, threadId); 3313 3314 if ((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) { 3315 ele.setName(tmpCursor.getString(fi.mConvoColName)); 3316 } 3317 3318 boolean reportRead = false; 3319 if ((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) { 3320 reportRead = true; 3321 } 3322 ele.setRead((1 == tmpCursor.getInt(fi.mConvoColRead)), reportRead); 3323 3324 long timestamp = tmpCursor.getLong(fi.mConvoColLastActivity); 3325 if ((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) { 3326 ele.setLastActivity(timestamp); 3327 } else { 3328 // We need to delete the time stamp, if it was added for multi msg-type 3329 ele.setLastActivity(-1); 3330 } 3331 3332 if ((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) { 3333 updateImEmailConvoVersion(tmpCursor, fi, ele); 3334 } 3335 if ((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) { 3336 ele.setSummary(tmpCursor.getString(fi.mConvoColSummary)); 3337 } 3338 // TODO: For optimization, we could avoid joining the contact and convo tables 3339 // if we have no filter nor this bit is set. 3340 if ((parameterMask & CONVO_PARAM_MASK_PARTICIPANTS) != 0) { 3341 do { 3342 BluetoothMapConvoContactElement c = new BluetoothMapConvoContactElement(); 3343 if ((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) { 3344 c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid), 0)); 3345 } 3346 if ((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) { 3347 c.setChatState(tmpCursor.getInt(fi.mContactColChatState)); 3348 } 3349 if ((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) { 3350 c.setPresenceAvailability(tmpCursor.getInt(fi.mContactColPresenceState)); 3351 } 3352 if ((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) { 3353 c.setPresenceStatus(tmpCursor.getString(fi.mContactColPresenceText)); 3354 } 3355 if ((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) { 3356 c.setPriority(tmpCursor.getInt(fi.mContactColPriority)); 3357 } 3358 if ((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) { 3359 c.setDisplayName(tmpCursor.getString(fi.mContactColNickname)); 3360 } 3361 if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) { 3362 c.setContactId(tmpCursor.getString(fi.mContactColContactUci)); 3363 } 3364 if ((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) { 3365 c.setLastActivity(tmpCursor.getLong(fi.mContactColLastActive)); 3366 } 3367 if ((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) { 3368 c.setName(tmpCursor.getString(fi.mContactColName)); 3369 } 3370 ele.addContact(c); 3371 } while (tmpCursor.moveToNext() && tmpCursor.getLong(fi.mConvoColConvoId) == threadId); 3372 } 3373 } 3374 3375 /** 3376 * Extract the ConvoList parameters from appParams and build the matching URI with query 3377 * parameters. 3378 * 3379 * @param ap the appParams from the request 3380 * @param contentUri the URI to append parameters to 3381 * @return the new URI with the appended parameters (if any) 3382 */ appendConvoListQueryParameters(BluetoothMapAppParams ap, Uri contentUri)3383 private static Uri appendConvoListQueryParameters(BluetoothMapAppParams ap, Uri contentUri) { 3384 Uri.Builder newUri = contentUri.buildUpon(); 3385 String str = ap.getFilterRecipient(); 3386 if (str != null) { 3387 str = str.trim(); 3388 str = str.replace("*", "%"); 3389 newUri.appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, str); 3390 } 3391 long time = ap.getFilterLastActivityBegin(); 3392 if (time > 0) { 3393 newUri.appendQueryParameter( 3394 BluetoothMapContract.FILTER_PERIOD_BEGIN, Long.toString(time)); 3395 } 3396 time = ap.getFilterLastActivityEnd(); 3397 if (time > 0) { 3398 newUri.appendQueryParameter( 3399 BluetoothMapContract.FILTER_PERIOD_END, Long.toString(time)); 3400 } 3401 int readStatus = ap.getFilterReadStatus(); 3402 if (readStatus > 0) { 3403 if (readStatus == 1) { 3404 // Conversations with Unread messages only 3405 newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS, "false"); 3406 } else if (readStatus == 2) { 3407 // Conversations with all read messages only 3408 newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS, "true"); 3409 } 3410 // if both are set it will be the same as requesting an empty list, but 3411 // as it makes no sense with such a structure in a bit mask, we treat 3412 // requesting both the same as no filtering. 3413 } 3414 long convoId = -1; 3415 if (ap.getFilterConvoId() != null) { 3416 convoId = ap.getFilterConvoId().leastSignificantBits(); 3417 } 3418 if (convoId > 0) { 3419 newUri.appendQueryParameter( 3420 BluetoothMapContract.FILTER_THREAD_ID, Long.toString(convoId)); 3421 } 3422 return newUri.build(); 3423 } 3424 3425 /** 3426 * Procedure if we have a filter: - loop through all ids to examine if there is a match (this 3427 * will build the cache) - If there is a match loop again to add all contacts. 3428 * 3429 * <p>Procedure if we don't have a filter - Add all contacts 3430 */ addSmsMmsContacts( BluetoothMapConvoListingElement convoElement, SmsMmsContacts contacts, String idsStr, String recipientFilter, BluetoothMapAppParams ap)3431 private boolean addSmsMmsContacts( 3432 BluetoothMapConvoListingElement convoElement, 3433 SmsMmsContacts contacts, 3434 String idsStr, 3435 String recipientFilter, 3436 BluetoothMapAppParams ap) { 3437 BluetoothMapConvoContactElement contactElement; 3438 int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value 3439 boolean foundContact = false; 3440 String[] ids = idsStr.split(" "); 3441 long[] longIds = new long[ids.length]; 3442 if (recipientFilter != null) { 3443 recipientFilter = recipientFilter.trim(); 3444 } 3445 3446 for (int i = 0; i < ids.length; i++) { 3447 long longId; 3448 try { 3449 longId = Long.parseLong(ids[i]); 3450 longIds[i] = longId; 3451 if (recipientFilter == null) { 3452 // If there is not filter, all we need to do is to parse the ids 3453 foundContact = true; 3454 continue; 3455 } 3456 String addr = contacts.getPhoneNumber(mResolver, longId); 3457 if (addr == null) { 3458 // This can only happen if all messages from a contact is deleted while 3459 // performing the query. 3460 continue; 3461 } 3462 MapContact contact = 3463 contacts.getContactNameFromPhone(addr, mResolver, recipientFilter); 3464 Log.d( 3465 TAG, 3466 "id: " 3467 + longId 3468 + ", addr: " 3469 + addr 3470 + ", contact name: " 3471 + (contact != null 3472 ? contact.name() + ", X-BT-UID: " + contact.getXBtUid() 3473 : "null")); 3474 if (contact == null) { 3475 continue; 3476 } 3477 foundContact = true; 3478 } catch (NumberFormatException ex) { 3479 ContentProfileErrorReportUtils.report( 3480 BluetoothProfile.MAP, 3481 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 3482 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 3483 4); 3484 } 3485 } 3486 3487 if (foundContact) { 3488 foundContact = false; 3489 for (long id : longIds) { 3490 String addr = contacts.getPhoneNumber(mResolver, id); 3491 if (addr == null) { 3492 // This can only happen if all messages from a contact is deleted while 3493 // performing the query. 3494 continue; 3495 } 3496 foundContact = true; 3497 MapContact contact = contacts.getContactNameFromPhone(addr, mResolver); 3498 3499 if (contact == null) { 3500 // We do not have a contact, we need to manually add one 3501 contactElement = new BluetoothMapConvoContactElement(); 3502 if ((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) { 3503 contactElement.setName(addr); // Use the phone number as name 3504 } 3505 if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) { 3506 contactElement.setContactId(addr); 3507 } 3508 } else { 3509 contactElement = 3510 BluetoothMapConvoContactElement.createFromMapContact(contact, addr); 3511 // Remove the parameters not to be reported 3512 if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) { 3513 contactElement.setContactId(null); 3514 } 3515 if ((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) { 3516 contactElement.setBtUid(null); 3517 } 3518 if ((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) { 3519 contactElement.setDisplayName(null); 3520 } 3521 } 3522 convoElement.addContact(contactElement); 3523 } 3524 } 3525 return foundContact; 3526 } 3527 3528 /** 3529 * Get the folder name of an SMS message or MMS message. 3530 * 3531 * @return the folder name. 3532 */ getFolderName(int type, int threadId)3533 private static String getFolderName(int type, int threadId) { 3534 3535 if (threadId == -1) { 3536 return BluetoothMapContract.FOLDER_NAME_DELETED; 3537 } 3538 3539 switch (type) { 3540 case 1: 3541 return BluetoothMapContract.FOLDER_NAME_INBOX; 3542 case 2: 3543 return BluetoothMapContract.FOLDER_NAME_SENT; 3544 case 3: 3545 return BluetoothMapContract.FOLDER_NAME_DRAFT; 3546 case 4: // Just name outbox, failed and queued "outbox" 3547 case 5: 3548 case 6: 3549 return BluetoothMapContract.FOLDER_NAME_OUTBOX; 3550 } 3551 return ""; 3552 } 3553 getMessage( String handle, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement, String version)3554 public byte[] getMessage( 3555 String handle, 3556 BluetoothMapAppParams appParams, 3557 BluetoothMapFolderElement folderElement, 3558 String version) 3559 throws UnsupportedEncodingException { 3560 TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle); 3561 mMessageVersion = version; 3562 long id = BluetoothMapUtils.getCpHandle(handle); 3563 if (appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) { 3564 throw new IllegalArgumentException( 3565 "FRACTION_REQUEST_NEXT does not make sense as" 3566 + " we always return the full message."); 3567 } 3568 switch (type) { 3569 case SMS_GSM: 3570 case SMS_CDMA: 3571 return getSmsMessage(id, appParams.getCharset()); 3572 case MMS: 3573 return getMmsMessage(id, appParams); 3574 case EMAIL: 3575 return getEmailMessage(id, appParams, folderElement); 3576 case IM: 3577 return getIMMessage(id, appParams, folderElement); 3578 default: 3579 throw new IllegalArgumentException("Invalid message handle."); 3580 } 3581 } 3582 setVCardFromPhoneNumber( BluetoothMapbMessage message, String phone, boolean incoming)3583 private String setVCardFromPhoneNumber( 3584 BluetoothMapbMessage message, String phone, boolean incoming) { 3585 String contactId = null, contactName = null; 3586 String[] phoneNumbers = new String[1]; 3587 // Handle possible exception for empty phone address 3588 if (TextUtils.isEmpty(phone)) { 3589 return contactName; 3590 } 3591 // 3592 // Use only actual phone number, because the MCE cannot know which 3593 // number the message is from. 3594 // 3595 phoneNumbers[0] = phone; 3596 String[] emailAddresses = null; 3597 Cursor p; 3598 3599 Uri uri = 3600 Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone)); 3601 3602 String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME}; 3603 String selection = Contacts.IN_VISIBLE_GROUP + "=1"; 3604 String orderBy = Contacts._ID + " ASC"; 3605 3606 // Get the contact _ID and name 3607 p = 3608 BluetoothMethodProxy.getInstance() 3609 .contentResolverQuery(mResolver, uri, projection, selection, null, orderBy); 3610 try { 3611 if (p != null && p.moveToFirst()) { 3612 contactId = p.getString(p.getColumnIndex(Contacts._ID)); 3613 contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME)); 3614 } 3615 } finally { 3616 close(p); 3617 } 3618 // Bail out if we are unable to find a contact, based on the phone number 3619 if (contactId != null) { 3620 Cursor q = null; 3621 // Fetch the contact e-mail addresses 3622 try { 3623 q = 3624 BluetoothMethodProxy.getInstance() 3625 .contentResolverQuery( 3626 mResolver, 3627 ContactsContract.CommonDataKinds.Email.CONTENT_URI, 3628 null, 3629 ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", 3630 new String[] {contactId}, 3631 null); 3632 if (q != null && q.moveToFirst()) { 3633 int i = 0; 3634 emailAddresses = new String[q.getCount()]; 3635 do { 3636 String emailAddress = 3637 q.getString( 3638 q.getColumnIndex( 3639 ContactsContract.CommonDataKinds.Email.ADDRESS)); 3640 emailAddresses[i++] = emailAddress; 3641 } while (q != null && q.moveToNext()); 3642 } 3643 } finally { 3644 close(q); 3645 } 3646 } 3647 3648 if (incoming) { 3649 Log.v(TAG, "Adding originator for phone:" + phone); 3650 // Use version 3.0 as we only have a formatted name 3651 message.addOriginator( 3652 contactName, contactName, phoneNumbers, emailAddresses, null, null); 3653 } else { 3654 Log.v(TAG, "Adding recipient for phone:" + phone); 3655 // Use version 3.0 as we only have a formatted name 3656 message.addRecipient( 3657 contactName, contactName, phoneNumbers, emailAddresses, null, null); 3658 } 3659 return contactName; 3660 } 3661 3662 public static final int MAP_MESSAGE_CHARSET_NATIVE = 0; 3663 public static final int MAP_MESSAGE_CHARSET_UTF8 = 1; 3664 getSmsMessage(long id, int charset)3665 public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException { 3666 int type, threadId; 3667 long time = -1; 3668 String msgBody; 3669 BluetoothMapbMessageSms message = new BluetoothMapbMessageSms(); 3670 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 3671 3672 Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null); 3673 if (c == null || !c.moveToFirst()) { 3674 throw new IllegalArgumentException("SMS handle not found"); 3675 } 3676 3677 try { 3678 if (c != null && c.moveToFirst()) { 3679 Log.v(TAG, "c.count: " + c.getCount()); 3680 3681 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 3682 message.setType(TYPE.SMS_CDMA); 3683 } else { 3684 // set SMS_GSM by default 3685 message.setType(TYPE.SMS_GSM); 3686 } 3687 message.setVersionString(mMessageVersion); 3688 String read = c.getString(c.getColumnIndex(Sms.READ)); 3689 if (read.equalsIgnoreCase("1")) { 3690 message.setStatus(true); 3691 } else { 3692 message.setStatus(false); 3693 } 3694 3695 type = c.getInt(c.getColumnIndex(Sms.TYPE)); 3696 threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 3697 message.setFolder(getFolderName(type, threadId)); 3698 3699 msgBody = c.getString(c.getColumnIndex(Sms.BODY)); 3700 3701 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); 3702 if ((phone == null) && type == Sms.MESSAGE_TYPE_DRAFT) { 3703 // Fetch address for Drafts folder from "canonical_address" table 3704 phone = getCanonicalAddressSms(mResolver, threadId); 3705 } 3706 time = c.getLong(c.getColumnIndex(Sms.DATE)); 3707 if (type == 1) { // Inbox message needs to set the vCard as originator 3708 setVCardFromPhoneNumber(message, phone, true); 3709 } else { // Other messages sets the vCard as the recipient 3710 setVCardFromPhoneNumber(message, phone, false); 3711 } 3712 if (charset == MAP_MESSAGE_CHARSET_NATIVE) { 3713 if (type == 1) { // Inbox 3714 message.setSmsBodyPdus( 3715 BluetoothMapSmsPdu.getDeliverPdus(mContext, msgBody, phone, time)); 3716 } else { 3717 message.setSmsBodyPdus( 3718 BluetoothMapSmsPdu.getSubmitPdus(mContext, msgBody, phone)); 3719 } 3720 } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ { 3721 message.setSmsBody(msgBody); 3722 } 3723 return message.encode(); 3724 } 3725 } finally { 3726 if (c != null) { 3727 c.close(); 3728 } 3729 } 3730 3731 return message.encode(); 3732 } 3733 3734 @VisibleForTesting extractMmsAddresses(long id, BluetoothMapbMessageMime message)3735 void extractMmsAddresses(long id, BluetoothMapbMessageMime message) { 3736 final String[] projection = null; 3737 String selection = new String(Mms.Addr.MSG_ID + "=" + id); 3738 String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr"); 3739 Uri uriAddress = Uri.parse(uriStr); 3740 String contactName = null; 3741 3742 Cursor c = 3743 BluetoothMethodProxy.getInstance() 3744 .contentResolverQuery( 3745 mResolver, uriAddress, projection, selection, null, null); 3746 try { 3747 if (c.moveToFirst()) { 3748 do { 3749 String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS)); 3750 if (address.equals(INSERT_ADDRESS_TOKEN)) { 3751 continue; 3752 } 3753 Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE)); 3754 switch (type) { 3755 case MMS_FROM: 3756 contactName = setVCardFromPhoneNumber(message, address, true); 3757 message.addFrom(contactName, address); 3758 break; 3759 case MMS_TO: 3760 contactName = setVCardFromPhoneNumber(message, address, false); 3761 message.addTo(contactName, address); 3762 break; 3763 case MMS_CC: 3764 contactName = setVCardFromPhoneNumber(message, address, false); 3765 message.addCc(contactName, address); 3766 break; 3767 case MMS_BCC: 3768 contactName = setVCardFromPhoneNumber(message, address, false); 3769 message.addBcc(contactName, address); 3770 break; 3771 default: 3772 break; 3773 } 3774 } while (c.moveToNext()); 3775 } 3776 } finally { 3777 if (c != null) { 3778 c.close(); 3779 } 3780 } 3781 } 3782 3783 /** 3784 * Read out a mime data part and return the data in a byte array. 3785 * 3786 * @param contentPartUri TODO 3787 * @param partid the content provider id of the Mime Part. 3788 */ readRawDataPart(Uri contentPartUri, long partid)3789 private byte[] readRawDataPart(Uri contentPartUri, long partid) { 3790 String uriStr = new String(contentPartUri + "/" + partid); 3791 Uri uriAddress = Uri.parse(uriStr); 3792 InputStream is = null; 3793 ByteArrayOutputStream os = new ByteArrayOutputStream(); 3794 int bufferSize = 8192; 3795 byte[] buffer = new byte[bufferSize]; 3796 byte[] retVal = null; 3797 3798 try { 3799 is = mResolver.openInputStream(uriAddress); 3800 int len = 0; 3801 while ((len = is.read(buffer)) != -1) { 3802 os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize 3803 } 3804 retVal = os.toByteArray(); 3805 } catch (IOException e) { 3806 ContentProfileErrorReportUtils.report( 3807 BluetoothProfile.MAP, 3808 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 3809 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 3810 5); 3811 // do nothing for now 3812 Log.w(TAG, "Error reading part data", e); 3813 } finally { 3814 close(os); 3815 close(is); 3816 } 3817 return retVal; 3818 } 3819 3820 /** 3821 * Read out the mms parts and update the bMessage object provided i {@code message} 3822 * 3823 * @param id the content provider ID of the message 3824 * @param message the bMessage object to add the information to 3825 */ 3826 @VisibleForTesting extractMmsParts(long id, BluetoothMapbMessageMime message)3827 void extractMmsParts(long id, BluetoothMapbMessageMime message) { 3828 final String[] projection = null; 3829 String selection = new String(Mms.Part.MSG_ID + "=" + id); 3830 String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part"); 3831 Uri uriAddress = Uri.parse(uriStr); 3832 BluetoothMapbMessageMime.MimePart part; 3833 Cursor c = 3834 BluetoothMethodProxy.getInstance() 3835 .contentResolverQuery( 3836 mResolver, uriAddress, projection, selection, null, null); 3837 try { 3838 if (c.moveToFirst()) { 3839 do { 3840 Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID)); 3841 String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE)); 3842 String name = c.getString(c.getColumnIndex(Mms.Part.NAME)); 3843 String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET)); 3844 String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME)); 3845 String text = c.getString(c.getColumnIndex(Mms.Part.TEXT)); 3846 Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA)); 3847 String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID)); 3848 String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION)); 3849 String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION)); 3850 3851 Log.v( 3852 TAG, 3853 " _id : " 3854 + partId 3855 + "\n ct : " 3856 + contentType 3857 + "\n partname : " 3858 + name 3859 + "\n charset : " 3860 + charset 3861 + "\n filename : " 3862 + filename 3863 + "\n text : " 3864 + text 3865 + "\n fd : " 3866 + fd 3867 + "\n cid : " 3868 + cid 3869 + "\n cl : " 3870 + cl 3871 + "\n cdisp : " 3872 + cdisp); 3873 3874 part = message.addMimePart(); 3875 part.mContentType = contentType; 3876 part.mPartName = name; 3877 part.mContentId = cid; 3878 part.mContentLocation = cl; 3879 part.mContentDisposition = cdisp; 3880 3881 // Filtering out non-text parts (e.g., an image) when attachments are to be 3882 // excluded is currently handled within the "message" object's encoding 3883 // function (c.f., BluetoothMapbMessageMime.encodeMime()), where the 3884 // attachment is replaced with a text string containing the part name or 3885 // filename. 3886 // However, replacing with text during encoding is too late, as charset 3887 // information does not get properly set and propagated. For example, if a MMS 3888 // consists only of a GIF, it's mimetype is "image/gif" and not "text", so 3889 // according to spec, "charset" should not be set. However, if the attachment 3890 // is replaced with a text string, the bMessage now contains text and should 3891 // have charset set to UTF-8 according to spec. 3892 if (!part.mContentType.toUpperCase(Locale.ROOT).contains("TEXT") 3893 && !message.getIncludeAttachments()) { 3894 StringBuilder sb = new StringBuilder(); 3895 part.encodePlainText(sb); 3896 // Each time {@code encodePlainText} is called, it adds {@code "\r\n"} 3897 // to the string. {@code encodePlainText} is called here to replace 3898 // an image with a string, but later on, when we encode the entire 3899 // bMessage in {@link BluetoothMapbMessageMime#encode()}, 3900 // {@code encodePlainText} will be called again on this {@code 3901 // MimePart} (as text this time), adding a second {@code "\r\n"}. So 3902 // we remove the extra newline from the end. 3903 int newlineIndex = sb.lastIndexOf("\r\n"); 3904 if (newlineIndex != -1) sb.delete(newlineIndex, newlineIndex + 4); 3905 text = sb.toString(); 3906 part.mContentType = "text"; 3907 } 3908 3909 try { 3910 if (text != null) { 3911 part.mData = text.getBytes(StandardCharsets.UTF_8); 3912 part.mCharsetName = "utf-8"; 3913 } else { 3914 part.mData = 3915 readRawDataPart(Uri.parse(Mms.CONTENT_URI + "/part"), partId); 3916 if (charset != null) { 3917 part.mCharsetName = 3918 CharacterSets.getMimeName(Integer.parseInt(charset)); 3919 } 3920 } 3921 } catch (NumberFormatException e) { 3922 ContentProfileErrorReportUtils.report( 3923 BluetoothProfile.MAP, 3924 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 3925 BluetoothStatsLog 3926 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 3927 7); 3928 Log.d(TAG, "extractMmsParts", e); 3929 part.mData = null; 3930 part.mCharsetName = null; 3931 } catch (UnsupportedEncodingException e) { 3932 ContentProfileErrorReportUtils.report( 3933 BluetoothProfile.MAP, 3934 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 3935 BluetoothStatsLog 3936 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 3937 8); 3938 Log.d(TAG, "extractMmsParts", e); 3939 part.mData = null; 3940 part.mCharsetName = null; 3941 } 3942 part.mFileName = filename; 3943 } while (c.moveToNext()); 3944 message.updateCharset(); 3945 } 3946 3947 } finally { 3948 if (c != null) { 3949 c.close(); 3950 } 3951 } 3952 } 3953 3954 /** 3955 * @param id the content provider id for the message to fetch. 3956 * @param appParams The application parameter object received from the client. 3957 * @return a byte[] containing the utf-8 encoded bMessage to send to the client. 3958 * @throws UnsupportedEncodingException if UTF-8 is not supported, which is guaranteed to be 3959 * supported on an android device 3960 */ getMmsMessage(long id, BluetoothMapAppParams appParams)3961 public byte[] getMmsMessage(long id, BluetoothMapAppParams appParams) 3962 throws UnsupportedEncodingException { 3963 int msgBox, threadId; 3964 if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) { 3965 throw new IllegalArgumentException( 3966 "MMS charset native not allowed for MMS" + " - must be utf-8"); 3967 } 3968 3969 BluetoothMapbMessageMime message = new BluetoothMapbMessageMime(); 3970 Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null); 3971 try { 3972 if (c != null && c.moveToFirst()) { 3973 message.setType(TYPE.MMS); 3974 message.setVersionString(mMessageVersion); 3975 3976 // The MMS info: 3977 String read = c.getString(c.getColumnIndex(Mms.READ)); 3978 if (read.equalsIgnoreCase("1")) { 3979 message.setStatus(true); 3980 } else { 3981 message.setStatus(false); 3982 } 3983 3984 msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 3985 threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 3986 message.setFolder(getFolderName(msgBox, threadId)); 3987 message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT))); 3988 message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID))); 3989 message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE))); 3990 message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L); 3991 message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) != 0); 3992 message.setIncludeAttachments(appParams.getAttachment() != 0); 3993 // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used 3994 // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is 3995 3996 // The parts 3997 extractMmsParts(id, message); 3998 3999 // The addresses 4000 extractMmsAddresses(id, message); 4001 4002 return message.encode(); 4003 } 4004 } finally { 4005 if (c != null) { 4006 c.close(); 4007 } 4008 } 4009 4010 return message.encode(); 4011 } 4012 4013 /** 4014 * @param id the content provider id for the message to fetch. 4015 * @param appParams The application parameter object received from the client. 4016 * @return a byte[] containing the utf-8 encoded bMessage to send to the client. 4017 * @throws UnsupportedEncodingException if UTF-8 is not supported, which is guaranteed to be 4018 * supported on an android device 4019 */ getEmailMessage( long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement currentFolder)4020 public byte[] getEmailMessage( 4021 long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement currentFolder) 4022 throws UnsupportedEncodingException { 4023 // Log print out of application parameters set 4024 if (appParams != null) { 4025 Log.d( 4026 TAG, 4027 "TYPE_MESSAGE (GET): Attachment = " 4028 + appParams.getAttachment() 4029 + ", Charset = " 4030 + appParams.getCharset() 4031 + ", FractionRequest = " 4032 + appParams.getFractionRequest()); 4033 } 4034 4035 // Throw exception if requester NATIVE charset for Email 4036 // Exception is caught by MapObexServer sendGetMessageResp 4037 if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) { 4038 throw new IllegalArgumentException("EMAIL charset not UTF-8"); 4039 } 4040 4041 BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail(); 4042 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 4043 Cursor c = 4044 BluetoothMethodProxy.getInstance() 4045 .contentResolverQuery( 4046 mResolver, 4047 contentUri, 4048 BluetoothMapContract.BT_MESSAGE_PROJECTION, 4049 "_ID = " + id, 4050 null, 4051 null); 4052 try { 4053 if (c != null && c.moveToFirst()) { 4054 BluetoothMapFolderElement folderElement; 4055 FileInputStream is = null; 4056 ParcelFileDescriptor fd = null; 4057 try { 4058 // Handle fraction requests 4059 int fractionRequest = appParams.getFractionRequest(); 4060 if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 4061 // Fraction requested 4062 String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT"; 4063 Log.v( 4064 TAG, 4065 "getEmailMessage - FractionRequest " 4066 + fractionStr 4067 + " - send compete message"); 4068 // Check if message is complete and if not - request message from server 4069 if (!c.getString( 4070 c.getColumnIndex( 4071 BluetoothMapContract.MessageColumns 4072 .RECEPTION_STATE)) 4073 .equalsIgnoreCase(BluetoothMapContract.RECEPTION_STATE_COMPLETE)) { 4074 // TODO: request message from server 4075 Log.w( 4076 TAG, 4077 "getEmailMessage - receptionState not COMPLETE - Not " 4078 + "Implemented!"); 4079 ContentProfileErrorReportUtils.report( 4080 BluetoothProfile.MAP, 4081 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 4082 BluetoothStatsLog 4083 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN, 4084 9); 4085 } 4086 } 4087 // Set read status: 4088 String read = 4089 c.getString( 4090 c.getColumnIndex( 4091 BluetoothMapContract.MessageColumns.FLAG_READ)); 4092 if (read != null && read.equalsIgnoreCase("1")) { 4093 message.setStatus(true); 4094 } else { 4095 message.setStatus(false); 4096 } 4097 4098 // Set message type: 4099 message.setType(TYPE.EMAIL); 4100 message.setVersionString(mMessageVersion); 4101 // Set folder: 4102 long folderId = 4103 c.getLong( 4104 c.getColumnIndex( 4105 BluetoothMapContract.MessageColumns.FOLDER_ID)); 4106 folderElement = currentFolder.getFolderById(folderId); 4107 message.setCompleteFolder(folderElement.getFullPath()); 4108 4109 // Set recipient: 4110 String nameEmail = 4111 c.getString( 4112 c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST)); 4113 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail); 4114 if (tokens.length != 0) { 4115 Log.d(TAG, "Recipient count= " + tokens.length); 4116 int i = 0; 4117 while (i < tokens.length) { 4118 Log.v(TAG, "Recipient = " + tokens[i].toString()); 4119 String[] emails = new String[1]; 4120 emails[0] = tokens[i].getAddress(); 4121 String name = tokens[i].getName(); 4122 message.addRecipient(name, name, null, emails, null, null); 4123 i++; 4124 } 4125 } 4126 4127 // Set originator: 4128 nameEmail = 4129 c.getString( 4130 c.getColumnIndex( 4131 BluetoothMapContract.MessageColumns.FROM_LIST)); 4132 tokens = Rfc822Tokenizer.tokenize(nameEmail); 4133 if (tokens.length != 0) { 4134 Log.d(TAG, "Originator count= " + tokens.length); 4135 int i = 0; 4136 while (i < tokens.length) { 4137 Log.v(TAG, "Originator = " + tokens[i].toString()); 4138 String[] emails = new String[1]; 4139 emails[0] = tokens[i].getAddress(); 4140 String name = tokens[i].getName(); 4141 message.addOriginator(name, name, null, emails, null, null); 4142 i++; 4143 } 4144 } 4145 } finally { 4146 if (c != null) { 4147 c.close(); 4148 } 4149 } 4150 // Find out if we get attachments 4151 String attStr = 4152 (appParams.getAttachment() == 0) 4153 ? "/" + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS 4154 : ""; 4155 Uri uri = Uri.parse(contentUri + "/" + id + attStr); 4156 4157 // Get email message body content 4158 int count = 0; 4159 try { 4160 fd = 4161 BluetoothMethodProxy.getInstance() 4162 .contentResolverOpenFileDescriptor(mResolver, uri, "r"); 4163 is = new FileInputStream(fd.getFileDescriptor()); 4164 StringBuilder email = new StringBuilder(""); 4165 byte[] buffer = new byte[1024]; 4166 while ((count = is.read(buffer)) != -1) { 4167 // TODO: Handle breaks within a UTF8 character 4168 email.append(new String(buffer, 0, count)); 4169 Log.v( 4170 TAG, 4171 "Email part = " + new String(buffer, 0, count) + " count=" + count); 4172 } 4173 // Set email message body: 4174 message.setEmailBody(email.toString()); 4175 } catch (FileNotFoundException e) { 4176 ContentProfileErrorReportUtils.report( 4177 BluetoothProfile.MAP, 4178 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 4179 BluetoothStatsLog 4180 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 4181 10); 4182 Log.w(TAG, e); 4183 } catch (NullPointerException e) { 4184 ContentProfileErrorReportUtils.report( 4185 BluetoothProfile.MAP, 4186 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 4187 BluetoothStatsLog 4188 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 4189 11); 4190 Log.w(TAG, e); 4191 } catch (IOException e) { 4192 ContentProfileErrorReportUtils.report( 4193 BluetoothProfile.MAP, 4194 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 4195 BluetoothStatsLog 4196 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 4197 12); 4198 Log.w(TAG, e); 4199 } finally { 4200 try { 4201 if (is != null) { 4202 is.close(); 4203 } 4204 } catch (IOException e) { 4205 ContentProfileErrorReportUtils.report( 4206 BluetoothProfile.MAP, 4207 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 4208 BluetoothStatsLog 4209 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 4210 13); 4211 Log.w(TAG, e); 4212 } 4213 try { 4214 if (fd != null) { 4215 fd.close(); 4216 } 4217 } catch (IOException e) { 4218 ContentProfileErrorReportUtils.report( 4219 BluetoothProfile.MAP, 4220 BluetoothProtoEnums.BLUETOOTH_MAP_CONTENT, 4221 BluetoothStatsLog 4222 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 4223 14); 4224 Log.w(TAG, e); 4225 } 4226 } 4227 return message.encode(); 4228 } 4229 } finally { 4230 if (c != null) { 4231 c.close(); 4232 } 4233 } 4234 throw new IllegalArgumentException("EMAIL handle not found"); 4235 } 4236 4237 /** 4238 * @param id the content provider id for the message to fetch. 4239 * @param appParams The application parameter object received from the client. 4240 * @return a byte[] containing the utf-8 encoded bMessage to send to the client. 4241 */ getIMMessage( long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement)4242 public byte[] getIMMessage( 4243 long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement) { 4244 long threadId, folderId; 4245 4246 if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) { 4247 throw new IllegalArgumentException( 4248 "IM charset native not allowed for IM - must be utf-8"); 4249 } 4250 4251 BluetoothMapbMessageMime message = new BluetoothMapbMessageMime(); 4252 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 4253 Cursor c = 4254 BluetoothMethodProxy.getInstance() 4255 .contentResolverQuery( 4256 mResolver, 4257 contentUri, 4258 BluetoothMapContract.BT_MESSAGE_PROJECTION, 4259 "_ID = " + id, 4260 null, 4261 null); 4262 Cursor contacts = null; 4263 try { 4264 if (c != null && c.moveToFirst()) { 4265 message.setType(TYPE.IM); 4266 message.setVersionString(mMessageVersion); 4267 4268 // The IM message info: 4269 int read = 4270 c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ)); 4271 if (read == 1) { 4272 message.setStatus(true); 4273 } else { 4274 message.setStatus(false); 4275 } 4276 4277 threadId = 4278 c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID)); 4279 folderId = 4280 c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID)); 4281 folderElement = folderElement.getFolderById(folderId); 4282 message.setCompleteFolder(folderElement.getFullPath()); 4283 message.setSubject( 4284 c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT))); 4285 message.setMessageId( 4286 c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID))); 4287 message.setDate( 4288 c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE))); 4289 message.setTextOnly( 4290 c.getInt( 4291 c.getColumnIndex( 4292 BluetoothMapContract.MessageColumns 4293 .ATTACHMENT_SIZE)) 4294 == 0); 4295 4296 message.setIncludeAttachments(appParams.getAttachment() != 0); 4297 4298 // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used 4299 // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is 4300 4301 // The parts 4302 4303 // FIXME next few lines are temporary code 4304 MimePart part = message.addMimePart(); 4305 part.mData = 4306 c.getString((c.getColumnIndex(BluetoothMapContract.MessageColumns.BODY))) 4307 .getBytes(StandardCharsets.UTF_8); 4308 part.mCharsetName = "utf-8"; 4309 part.mContentId = "0"; 4310 part.mContentType = "text/plain"; 4311 message.updateCharset(); 4312 // FIXME end temp code 4313 4314 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); 4315 contacts = 4316 BluetoothMethodProxy.getInstance() 4317 .contentResolverQuery( 4318 mResolver, 4319 contactsUri, 4320 BluetoothMapContract.BT_CONTACT_PROJECTION, 4321 BluetoothMapContract.ConvoContactColumns.CONVO_ID 4322 + " = " 4323 + threadId, 4324 null, 4325 null); 4326 // TODO this will not work for group-chats 4327 if (contacts != null && contacts.moveToFirst()) { 4328 String name = 4329 contacts.getString( 4330 contacts.getColumnIndex( 4331 BluetoothMapContract.ConvoContactColumns.NAME)); 4332 String[] btUid = new String[1]; 4333 btUid[0] = 4334 contacts.getString( 4335 contacts.getColumnIndex( 4336 BluetoothMapContract.ConvoContactColumns.X_BT_UID)); 4337 String nickname = 4338 contacts.getString( 4339 contacts.getColumnIndex( 4340 BluetoothMapContract.ConvoContactColumns.NICKNAME)); 4341 String[] btUci = new String[1]; 4342 String[] btOwnUci = new String[1]; 4343 btOwnUci[0] = mAccount.getUciFull(); 4344 btUci[0] = 4345 contacts.getString( 4346 contacts.getColumnIndex( 4347 BluetoothMapContract.ConvoContactColumns.UCI)); 4348 if (folderId == BluetoothMapContract.FOLDER_ID_SENT 4349 || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) { 4350 message.addRecipient(nickname, name, null, null, btUid, btUci); 4351 message.addOriginator(null, btOwnUci); 4352 4353 } else { 4354 message.addOriginator(nickname, name, null, null, btUid, btUci); 4355 message.addRecipient(null, btOwnUci); 4356 } 4357 } 4358 return message.encode(); 4359 } 4360 } finally { 4361 if (c != null) { 4362 c.close(); 4363 } 4364 if (contacts != null) { 4365 contacts.close(); 4366 } 4367 } 4368 4369 throw new IllegalArgumentException("IM handle not found"); 4370 } 4371 setRemoteFeatureMask(int featureMask)4372 public void setRemoteFeatureMask(int featureMask) { 4373 this.mRemoteFeatureMask = featureMask; 4374 Log.v(TAG, "setRemoteFeatureMask"); 4375 if ((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) 4376 == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) { 4377 Log.v(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11"); 4378 this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11; 4379 } 4380 } 4381 getRemoteFeatureMask()4382 public int getRemoteFeatureMask() { 4383 return this.mRemoteFeatureMask; 4384 } 4385 getSmsMmsConvoList()4386 Map<Long, BluetoothMapConvoListingElement> getSmsMmsConvoList() { 4387 return mMasInstance.getSmsMmsConvoList(); 4388 } 4389 setSmsMmsConvoList(Map<Long, BluetoothMapConvoListingElement> smsMmsConvoList)4390 void setSmsMmsConvoList(Map<Long, BluetoothMapConvoListingElement> smsMmsConvoList) { 4391 mMasInstance.setSmsMmsConvoList(smsMmsConvoList); 4392 } 4393 getImEmailConvoList()4394 Map<Long, BluetoothMapConvoListingElement> getImEmailConvoList() { 4395 return mMasInstance.getImEmailConvoList(); 4396 } 4397 setImEmailConvoList(Map<Long, BluetoothMapConvoListingElement> imEmailConvoList)4398 void setImEmailConvoList(Map<Long, BluetoothMapConvoListingElement> imEmailConvoList) { 4399 mMasInstance.setImEmailConvoList(imEmailConvoList); 4400 } 4401 } 4402