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