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