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