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