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