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 // If a draft message has no recipient, it has no thread ID 956 // hence threadIdStr could possibly be null 957 if (threadIdStr != null) { 958 address = getCanonicalAddressSms(mResolver, Integer.valueOf(threadIdStr)); 959 } 960 if(V) Log.v(TAG, "threadId = " + threadIdStr + " adress:" + address +"\n"); 961 } 962 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 963 long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); 964 address = getAddressMms(mResolver, id, MMS_TO); 965 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 966 /* Might be another way to handle addresses */ 967 address = getRecipientAddressingEmail(e, c, fi); 968 } 969 if (V) Log.v(TAG, "setRecipientAddressing: " + address); 970 if(address == null) 971 address = ""; 972 e.setRecipientAddressing(address); 973 } 974 } 975 setRecipientName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)976 private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c, 977 FilterInfo fi, BluetoothMapAppParams ap) { 978 if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) { 979 String name = null; 980 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 981 int msgType = c.getInt(fi.mSmsColType); 982 if (msgType != 1) { 983 String phone = c.getString(fi.mSmsColAddress); 984 if (phone != null && !phone.isEmpty()) 985 name = getContactNameFromPhone(phone, mResolver); 986 } else { 987 name = fi.mPhoneAlphaTag; 988 } 989 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 990 long id = c.getLong(fi.mMmsColId); 991 String phone; 992 if(e.getRecipientAddressing() != null){ 993 phone = getAddressMms(mResolver, id, MMS_TO); 994 } else { 995 phone = e.getRecipientAddressing(); 996 } 997 if (phone != null && !phone.isEmpty()) 998 name = getContactNameFromPhone(phone, mResolver); 999 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 1000 /* Might be another way to handle address and names */ 1001 name = getRecipientNameEmail(e,c,fi); 1002 } 1003 if (V) Log.v(TAG, "setRecipientName: " + name); 1004 if(name == null) 1005 name = ""; 1006 e.setRecipientName(name); 1007 } 1008 } 1009 setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1010 private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c, 1011 FilterInfo fi, BluetoothMapAppParams ap) { 1012 if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) { 1013 String address = ""; 1014 String tempAddress; 1015 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1016 int msgType = c.getInt(fi.mSmsColType); 1017 if (msgType == 1) { // INBOX 1018 tempAddress = c.getString(fi.mSmsColAddress); 1019 } else { 1020 tempAddress = fi.mPhoneNum; 1021 } 1022 if(tempAddress == null) { 1023 /* This can only happen on devices with no SIM - 1024 hence will typically not have any SMS messages. */ 1025 } else { 1026 address = PhoneNumberUtils.extractNetworkPortion(tempAddress); 1027 /* extractNetworkPortion can return N if the number is a service "number" = 1028 * a string with the a name in (i.e. "Some-Tele-company" would return N 1029 * because of the N in compaNy) 1030 * Hence we need to check if the number is actually a string with alpha chars. 1031 * */ 1032 Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress).matches( 1033 "[0-9]*[a-zA-Z]+[0-9]*"); 1034 1035 if(address == null || address.length() < 2 || alpha) { 1036 address = tempAddress; // if the number is a service acsii text just use it 1037 } 1038 } 1039 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1040 long id = c.getLong(fi.mMmsColId); 1041 tempAddress = getAddressMms(mResolver, id, MMS_FROM); 1042 address = PhoneNumberUtils.extractNetworkPortion(tempAddress); 1043 if(address == null || address.length() < 1){ 1044 address = tempAddress; // if the number is a service acsii text just use it 1045 } 1046 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* || 1047 fi.mMsgType == FilterInfo.TYPE_IM*/) { 1048 String nameEmail = c.getString(fi.mMessageColFromAddress); 1049 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail); 1050 if (tokens.length != 0) { 1051 if(D) Log.d(TAG, "Originator count= " + tokens.length); 1052 int i = 0; 1053 boolean first = true; 1054 while (i < tokens.length) { 1055 if(V) Log.d(TAG, "SenderAddress = " + tokens[i].toString()); 1056 String[] emails = new String[1]; 1057 emails[0] = tokens[i].getAddress(); 1058 String name = tokens[i].getName(); 1059 if(!first) address += "; "; //Delimiter 1060 address += emails[0]; 1061 first = false; 1062 i++; 1063 } 1064 } 1065 } else if(fi.mMsgType == FilterInfo.TYPE_IM) { 1066 // TODO: For IM we add the contact ID in the addressing 1067 long contact_id = c.getLong(fi.mMessageColFromAddress); 1068 // TODO: This is a BAD hack, that we map the contact ID to a conversation ID!!! 1069 // We need to reach a conclusion on what to do 1070 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); 1071 Cursor contacts = mResolver.query(contactsUri, 1072 BluetoothMapContract.BT_CONTACT_PROJECTION, 1073 BluetoothMapContract.ConvoContactColumns.CONVO_ID 1074 + " = " + contact_id, null, null); 1075 try { 1076 // TODO this will not work for group-chats 1077 if(contacts != null && contacts.moveToFirst()){ 1078 address = contacts.getString( 1079 contacts.getColumnIndex( 1080 BluetoothMapContract.ConvoContactColumns.UCI)); 1081 } 1082 } finally { 1083 if (contacts != null) contacts.close(); 1084 } 1085 1086 } 1087 if (V) Log.v(TAG, "setSenderAddressing: " + address); 1088 if(address == null) 1089 address = ""; 1090 e.setSenderAddressing(address); 1091 } 1092 } 1093 setSenderName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1094 private void setSenderName(BluetoothMapMessageListingElement e, Cursor c, 1095 FilterInfo fi, BluetoothMapAppParams ap) { 1096 if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) { 1097 String name = ""; 1098 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1099 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); 1100 if (msgType == 1) { 1101 String phone = c.getString(fi.mSmsColAddress); 1102 if (phone != null && !phone.isEmpty()) 1103 name = getContactNameFromPhone(phone, mResolver); 1104 } else { 1105 name = fi.mPhoneAlphaTag; 1106 } 1107 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1108 long id = c.getLong(fi.mMmsColId); 1109 String phone; 1110 if(e.getSenderAddressing() != null){ 1111 phone = getAddressMms(mResolver, id, MMS_FROM); 1112 } else { 1113 phone = e.getSenderAddressing(); 1114 } 1115 if (phone != null && !phone.isEmpty() ) 1116 name = getContactNameFromPhone(phone, mResolver); 1117 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* || 1118 fi.mMsgType == FilterInfo.TYPE_IM*/) { 1119 String nameEmail = c.getString(fi.mMessageColFromAddress); 1120 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail); 1121 if (tokens.length != 0) { 1122 if(D) Log.d(TAG, "Originator count= " + tokens.length); 1123 int i = 0; 1124 boolean first = true; 1125 while (i < tokens.length) { 1126 if(V) Log.d(TAG, "senderName = " + tokens[i].toString()); 1127 String[] emails = new String[1]; 1128 emails[0] = tokens[i].getAddress(); 1129 String nameIn = tokens[i].getName(); 1130 if(!first) name += "; "; //Delimiter 1131 name += nameIn; 1132 first = false; 1133 i++; 1134 } 1135 } 1136 } else if(fi.mMsgType == FilterInfo.TYPE_IM) { 1137 // For IM we add the contact ID in the addressing 1138 long contact_id = c.getLong(fi.mMessageColFromAddress); 1139 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); 1140 Cursor contacts = mResolver.query(contactsUri, 1141 BluetoothMapContract.BT_CONTACT_PROJECTION, 1142 BluetoothMapContract.ConvoContactColumns.CONVO_ID 1143 + " = " + contact_id, null, null); 1144 try { 1145 // TODO this will not work for group-chats 1146 if(contacts != null && contacts.moveToFirst()){ 1147 name = contacts.getString( 1148 contacts.getColumnIndex( 1149 BluetoothMapContract.ConvoContactColumns.NAME)); 1150 } 1151 } finally { 1152 if (contacts != null) contacts.close(); 1153 } 1154 } 1155 if (V) Log.v(TAG, "setSenderName: " + name); 1156 if(name == null) 1157 name = ""; 1158 e.setSenderName(name); 1159 } 1160 } 1161 1162 1163 1164 setDateTime(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1165 private void setDateTime(BluetoothMapMessageListingElement e, Cursor c, 1166 FilterInfo fi, BluetoothMapAppParams ap) { 1167 if ((ap.getParameterMask() & MASK_DATETIME) != 0) { 1168 long date = 0; 1169 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1170 date = c.getLong(fi.mSmsColDate); 1171 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1172 /* Use Mms.DATE for all messages. Although contract class states */ 1173 /* Mms.DATE_SENT are for outgoing messages. But that is not working. */ 1174 date = c.getLong(fi.mMmsColDate) * 1000L; 1175 1176 /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */ 1177 /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */ 1178 /* date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */ 1179 /* } else { */ 1180 /* date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */ 1181 /* } */ 1182 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || 1183 fi.mMsgType == FilterInfo.TYPE_IM) { 1184 date = c.getLong(fi.mMessageColDate); 1185 } 1186 e.setDateTime(date); 1187 } 1188 } 1189 1190 setLastActivity(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1191 private void setLastActivity(BluetoothMapConvoListingElement e, Cursor c, 1192 FilterInfo fi, BluetoothMapAppParams ap) { 1193 long date = 0; 1194 if (fi.mMsgType == FilterInfo.TYPE_SMS || 1195 fi.mMsgType == FilterInfo.TYPE_MMS ) { 1196 date = c.getLong(MMS_SMS_THREAD_COL_DATE); 1197 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL|| 1198 fi.mMsgType == FilterInfo.TYPE_IM) { 1199 date = c.getLong(fi.mConvoColLastActivity); 1200 } 1201 e.setLastActivity(date); 1202 if (V) Log.v(TAG, "setDateTime: " + e.getLastActivityString()); 1203 1204 } 1205 getTextPartsMms(ContentResolver r, long id)1206 static public String getTextPartsMms(ContentResolver r, long id) { 1207 String text = ""; 1208 String selection = new String("mid=" + id); 1209 String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part"); 1210 Uri uriAddress = Uri.parse(uriStr); 1211 // TODO: maybe use a projection with only "ct" and "text" 1212 Cursor c = r.query(uriAddress, null, selection, 1213 null, null); 1214 try { 1215 if (c != null && c.moveToFirst()) { 1216 do { 1217 String ct = c.getString(c.getColumnIndex("ct")); 1218 if (ct.equals("text/plain")) { 1219 String part = c.getString(c.getColumnIndex("text")); 1220 if(part != null) { 1221 text += part; 1222 } 1223 } 1224 } while(c.moveToNext()); 1225 } 1226 } finally { 1227 if (c != null) c.close(); 1228 } 1229 1230 return text; 1231 } 1232 setSubject(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1233 private void setSubject(BluetoothMapMessageListingElement e, Cursor c, 1234 FilterInfo fi, BluetoothMapAppParams ap) { 1235 String subject = ""; 1236 int subLength = ap.getSubjectLength(); 1237 if(subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 1238 subLength = 256; 1239 1240 if ((ap.getParameterMask() & MASK_SUBJECT) != 0) { 1241 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1242 subject = c.getString(fi.mSmsColSubject); 1243 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1244 subject = c.getString(fi.mMmsColSubject); 1245 if (subject == null || subject.length() == 0) { 1246 /* Get subject from mms text body parts - if any exists */ 1247 long id = c.getLong(fi.mMmsColId); 1248 subject = getTextPartsMms(mResolver, id); 1249 } 1250 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || 1251 fi.mMsgType == FilterInfo.TYPE_IM) { 1252 subject = c.getString(fi.mMessageColSubject); 1253 } 1254 if (subject != null && subject.length() > subLength) { 1255 subject = subject.substring(0, subLength); 1256 } else if (subject == null ) { 1257 subject = ""; 1258 } 1259 if (V) Log.d(TAG, "setSubject: " + subject); 1260 e.setSubject(subject); 1261 } 1262 } 1263 setHandle(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1264 private void setHandle(BluetoothMapMessageListingElement e, Cursor c, 1265 FilterInfo fi, BluetoothMapAppParams ap) { 1266 long handle = -1; 1267 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1268 handle = c.getLong(fi.mSmsColId); 1269 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1270 handle = c.getLong(fi.mMmsColId); 1271 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || 1272 fi.mMsgType == FilterInfo.TYPE_IM) { 1273 handle = c.getLong(fi.mMessageColId); 1274 } 1275 if (V) Log.d(TAG, "setHandle: " + handle ); 1276 e.setHandle(handle); 1277 } 1278 element(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1279 private BluetoothMapMessageListingElement element(Cursor c, FilterInfo fi, 1280 BluetoothMapAppParams ap) { 1281 BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement(); 1282 setHandle(e, c, fi, ap); 1283 setDateTime(e, c, fi, ap); 1284 e.setType(getType(c, fi), ((ap.getParameterMask() & MASK_TYPE) != 0) ? true : false); 1285 setRead(e, c, fi, ap); 1286 // we set number and name for sender/recipient later 1287 // they require lookup on contacts so no need to 1288 // do it for all elements unless they are to be used. 1289 e.setCursorIndex(c.getPosition()); 1290 return e; 1291 } 1292 createConvoElement(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1293 private BluetoothMapConvoListingElement createConvoElement(Cursor c, FilterInfo fi, 1294 BluetoothMapAppParams ap) { 1295 BluetoothMapConvoListingElement e = new BluetoothMapConvoListingElement(); 1296 setLastActivity(e, c, fi, ap); 1297 e.setType(getType(c, fi)); 1298 // setConvoRead(e, c, fi, ap); 1299 e.setCursorIndex(c.getPosition()); 1300 return e; 1301 } 1302 1303 /* TODO: Change to use SmsMmsContacts.getContactNameFromPhone() with proper use of 1304 * caching. */ getContactNameFromPhone(String phone, ContentResolver resolver)1305 public static String getContactNameFromPhone(String phone, ContentResolver resolver) { 1306 String name = null; 1307 //Handle possible exception for empty phone address 1308 if (TextUtils.isEmpty(phone)) { 1309 return name; 1310 } 1311 1312 Uri uri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, 1313 Uri.encode(phone)); 1314 1315 String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME}; 1316 String selection = Contacts.IN_VISIBLE_GROUP + "=1"; 1317 String orderBy = Contacts.DISPLAY_NAME + " ASC"; 1318 Cursor c = null; 1319 try { 1320 c = resolver.query(uri, projection, selection, null, orderBy); 1321 if(c != null) { 1322 int colIndex = c.getColumnIndex(Contacts.DISPLAY_NAME); 1323 if (c.getCount() >= 1) { 1324 c.moveToFirst(); 1325 name = c.getString(colIndex); 1326 } 1327 } 1328 } finally { 1329 if(c != null) c.close(); 1330 } 1331 return name; 1332 } 1333 /** 1334 * Get SMS RecipientAddresses for DRAFT folder based on threadId 1335 * 1336 */ getCanonicalAddressSms(ContentResolver r, int threadId)1337 static public String getCanonicalAddressSms(ContentResolver r, int threadId) { 1338 String [] RECIPIENT_ID_PROJECTION = { Threads.RECIPIENT_IDS }; 1339 /* 1340 1. Get Recipient Ids from Threads.CONTENT_URI 1341 2. Get Recipient Address for corresponding Id from canonical-addresses table. 1342 */ 1343 1344 //Uri sAllCanonical = Uri.parse("content://mms-sms/canonical-addresses"); 1345 Uri sAllCanonical = 1346 MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build(); 1347 Uri sAllThreadsUri = 1348 Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); 1349 Cursor cr = null; 1350 String recipientAddress = ""; 1351 String recipientIds = null; 1352 String whereClause = "_id="+threadId; 1353 if (V) Log.v(TAG, "whereClause is "+ whereClause); 1354 try { 1355 cr = r.query(sAllThreadsUri, RECIPIENT_ID_PROJECTION, whereClause, null, null); 1356 if (cr != null && cr.moveToFirst()) { 1357 recipientIds = cr.getString(0); 1358 if (V) Log.v(TAG, "cursor.getCount(): " + cr.getCount() + "recipientIds: " 1359 + recipientIds + "selection: "+ whereClause ); 1360 } 1361 } finally { 1362 if(cr != null) { 1363 cr.close(); 1364 cr = null; 1365 } 1366 } 1367 if (V) Log.v(TAG, "recipientIds with spaces: "+ recipientIds +"\n"); 1368 if(recipientIds != null) { 1369 String recipients[] = null; 1370 whereClause = ""; 1371 recipients = recipientIds.split(" "); 1372 for (String id: recipients) { 1373 if(whereClause.length() != 0) 1374 whereClause +=" OR "; 1375 whereClause +="_id="+id; 1376 } 1377 if (V) Log.v(TAG, "whereClause is "+ whereClause); 1378 try { 1379 cr = r.query(sAllCanonical , null, whereClause, null, null); 1380 if (cr != null && cr.moveToFirst()) { 1381 do { 1382 //TODO: Multiple Recipeints are appended with ";" for now. 1383 if(recipientAddress.length() != 0 ) 1384 recipientAddress+=";"; 1385 recipientAddress += cr.getString( 1386 cr.getColumnIndex(CanonicalAddressesColumns.ADDRESS)); 1387 } while(cr.moveToNext()); 1388 } 1389 } finally { 1390 if(cr != null) 1391 cr.close(); 1392 } 1393 } 1394 1395 if(V) Log.v(TAG,"Final recipientAddress : "+ recipientAddress); 1396 return recipientAddress; 1397 } 1398 getAddressMms(ContentResolver r, long id, int type)1399 static public String getAddressMms(ContentResolver r, long id, int type) { 1400 String selection = new String("msg_id=" + id + " AND type=" + type); 1401 String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr"); 1402 Uri uriAddress = Uri.parse(uriStr); 1403 String addr = null; 1404 String[] projection = {Mms.Addr.ADDRESS}; 1405 Cursor c = null; 1406 try { 1407 c = r.query(uriAddress, projection, selection, null, null); // TODO: Add projection 1408 int colIndex = c.getColumnIndex(Mms.Addr.ADDRESS); 1409 if (c != null) { 1410 if(c.moveToFirst()) { 1411 addr = c.getString(colIndex); 1412 if(addr.equals(INSERT_ADDRES_TOKEN)) { 1413 addr = ""; 1414 } 1415 } 1416 } 1417 } finally { 1418 if (c != null) c.close(); 1419 } 1420 return addr; 1421 } 1422 1423 /** 1424 * Matching functions for originator and recipient for MMS 1425 * @return true if found a match 1426 */ matchRecipientMms(Cursor c, FilterInfo fi, String recip)1427 private boolean matchRecipientMms(Cursor c, FilterInfo fi, String recip) { 1428 boolean res; 1429 long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); 1430 String phone = getAddressMms(mResolver, id, MMS_TO); 1431 if (phone != null && phone.length() > 0) { 1432 if (phone.matches(recip)) { 1433 if (V) Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone); 1434 res = true; 1435 } else { 1436 String name = getContactNameFromPhone(phone, mResolver); 1437 if (name != null && name.length() > 0 && name.matches(recip)) { 1438 if (V) Log.v(TAG, "matchRecipientMms: match recipient name = " + name); 1439 res = true; 1440 } else { 1441 res = false; 1442 } 1443 } 1444 } else { 1445 res = false; 1446 } 1447 return res; 1448 } 1449 matchRecipientSms(Cursor c, FilterInfo fi, String recip)1450 private boolean matchRecipientSms(Cursor c, FilterInfo fi, String recip) { 1451 boolean res; 1452 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); 1453 if (msgType == 1) { 1454 String phone = fi.mPhoneNum; 1455 String name = fi.mPhoneAlphaTag; 1456 if (phone != null && phone.length() > 0 && phone.matches(recip)) { 1457 if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone); 1458 res = true; 1459 } else if (name != null && name.length() > 0 && name.matches(recip)) { 1460 if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name); 1461 res = true; 1462 } else { 1463 res = false; 1464 } 1465 } else { 1466 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); 1467 if (phone != null && phone.length() > 0) { 1468 if (phone.matches(recip)) { 1469 if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone); 1470 res = true; 1471 } else { 1472 String name = getContactNameFromPhone(phone, mResolver); 1473 if (name != null && name.length() > 0 && name.matches(recip)) { 1474 if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name); 1475 res = true; 1476 } else { 1477 res = false; 1478 } 1479 } 1480 } else { 1481 res = false; 1482 } 1483 } 1484 return res; 1485 } 1486 matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1487 private boolean matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { 1488 boolean res; 1489 String recip = ap.getFilterRecipient(); 1490 if (recip != null && recip.length() > 0) { 1491 recip = recip.replace("*", ".*"); 1492 recip = ".*" + recip + ".*"; 1493 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1494 res = matchRecipientSms(c, fi, recip); 1495 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1496 res = matchRecipientMms(c, fi, recip); 1497 } else { 1498 if (D) Log.d(TAG, "matchRecipient: Unknown msg type: " + fi.mMsgType); 1499 res = false; 1500 } 1501 } else { 1502 res = true; 1503 } 1504 return res; 1505 } 1506 matchOriginatorMms(Cursor c, FilterInfo fi, String orig)1507 private boolean matchOriginatorMms(Cursor c, FilterInfo fi, String orig) { 1508 boolean res; 1509 long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); 1510 String phone = getAddressMms(mResolver, id, MMS_FROM); 1511 if (phone != null && phone.length() > 0) { 1512 if (phone.matches(orig)) { 1513 if (V) Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone); 1514 res = true; 1515 } else { 1516 String name = getContactNameFromPhone(phone, mResolver); 1517 if (name != null && name.length() > 0 && name.matches(orig)) { 1518 if (V) Log.v(TAG, "matchOriginatorMms: match originator name = " + name); 1519 res = true; 1520 } else { 1521 res = false; 1522 } 1523 } 1524 } else { 1525 res = false; 1526 } 1527 return res; 1528 } 1529 matchOriginatorSms(Cursor c, FilterInfo fi, String orig)1530 private boolean matchOriginatorSms(Cursor c, FilterInfo fi, String orig) { 1531 boolean res; 1532 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); 1533 if (msgType == 1) { 1534 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); 1535 if (phone !=null && phone.length() > 0) { 1536 if (phone.matches(orig)) { 1537 if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone); 1538 res = true; 1539 } else { 1540 String name = getContactNameFromPhone(phone, mResolver); 1541 if (name != null && name.length() > 0 && name.matches(orig)) { 1542 if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name); 1543 res = true; 1544 } else { 1545 res = false; 1546 } 1547 } 1548 } else { 1549 res = false; 1550 } 1551 } else { 1552 String phone = fi.mPhoneNum; 1553 String name = fi.mPhoneAlphaTag; 1554 if (phone != null && phone.length() > 0 && phone.matches(orig)) { 1555 if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone); 1556 res = true; 1557 } else if (name != null && name.length() > 0 && name.matches(orig)) { 1558 if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name); 1559 res = true; 1560 } else { 1561 res = false; 1562 } 1563 } 1564 return res; 1565 } 1566 matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1567 private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { 1568 boolean res; 1569 String orig = ap.getFilterOriginator(); 1570 if (orig != null && orig.length() > 0) { 1571 orig = orig.replace("*", ".*"); 1572 orig = ".*" + orig + ".*"; 1573 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1574 res = matchOriginatorSms(c, fi, orig); 1575 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1576 res = matchOriginatorMms(c, fi, orig); 1577 } else { 1578 if(D) Log.d(TAG, "matchOriginator: Unknown msg type: " + fi.mMsgType); 1579 res = false; 1580 } 1581 } else { 1582 res = true; 1583 } 1584 return res; 1585 } 1586 matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)1587 private boolean matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { 1588 if (matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap)) { 1589 return true; 1590 } else { 1591 return false; 1592 } 1593 } 1594 1595 /* 1596 * Where filter functions 1597 * */ setWhereFilterFolderTypeSms(String folder)1598 private String setWhereFilterFolderTypeSms(String folder) { 1599 String where = ""; 1600 if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) { 1601 where = Sms.TYPE + " = 1 AND " + Sms.THREAD_ID + " <> -1"; 1602 } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) { 1603 where = "(" + Sms.TYPE + " = 4 OR " + Sms.TYPE + " = 5 OR " 1604 + Sms.TYPE + " = 6) AND " + Sms.THREAD_ID + " <> -1"; 1605 } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) { 1606 where = Sms.TYPE + " = 2 AND " + Sms.THREAD_ID + " <> -1"; 1607 } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) { 1608 where = Sms.TYPE + " = 3 AND " + 1609 "(" + Sms.THREAD_ID + " IS NULL OR " + Sms.THREAD_ID + " <> -1 )"; 1610 } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) { 1611 where = Sms.THREAD_ID + " = -1"; 1612 } 1613 1614 return where; 1615 } 1616 setWhereFilterFolderTypeMms(String folder)1617 private String setWhereFilterFolderTypeMms(String folder) { 1618 String where = ""; 1619 if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) { 1620 where = Mms.MESSAGE_BOX + " = 1 AND " + Mms.THREAD_ID + " <> -1"; 1621 } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) { 1622 where = Mms.MESSAGE_BOX + " = 4 AND " + Mms.THREAD_ID + " <> -1"; 1623 } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) { 1624 where = Mms.MESSAGE_BOX + " = 2 AND " + Mms.THREAD_ID + " <> -1"; 1625 } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) { 1626 where = Mms.MESSAGE_BOX + " = 3 AND " + 1627 "(" + Mms.THREAD_ID + " IS NULL OR " + Mms.THREAD_ID + " <> -1 )"; 1628 } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) { 1629 where = Mms.THREAD_ID + " = -1"; 1630 } 1631 1632 return where; 1633 } 1634 setWhereFilterFolderTypeEmail(long folderId)1635 private String setWhereFilterFolderTypeEmail(long folderId) { 1636 String where = ""; 1637 if (folderId >= 0) { 1638 where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId; 1639 } else { 1640 Log.e(TAG, "setWhereFilterFolderTypeEmail: not valid!" ); 1641 throw new IllegalArgumentException("Invalid folder ID"); 1642 } 1643 return where; 1644 } 1645 setWhereFilterFolderTypeIm(long folderId)1646 private String setWhereFilterFolderTypeIm(long folderId) { 1647 String where = ""; 1648 if (folderId > BluetoothMapContract.FOLDER_ID_OTHER) { 1649 where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId; 1650 } else { 1651 Log.e(TAG, "setWhereFilterFolderTypeIm: not valid!" ); 1652 throw new IllegalArgumentException("Invalid folder ID"); 1653 } 1654 return where; 1655 } 1656 setWhereFilterFolderType(BluetoothMapFolderElement folderElement, FilterInfo fi)1657 private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement, 1658 FilterInfo fi) { 1659 String where = "1=1"; 1660 if (!folderElement.shouldIgnore()) { 1661 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1662 where = setWhereFilterFolderTypeSms(folderElement.getName()); 1663 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1664 where = setWhereFilterFolderTypeMms(folderElement.getName()); 1665 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 1666 where = setWhereFilterFolderTypeEmail(folderElement.getFolderId()); 1667 } else if (fi.mMsgType == FilterInfo.TYPE_IM) { 1668 where = setWhereFilterFolderTypeIm(folderElement.getFolderId()); 1669 } 1670 } 1671 1672 return where; 1673 } 1674 setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi)1675 private String setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi) { 1676 String where = ""; 1677 if (ap.getFilterReadStatus() != -1) { 1678 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1679 if ((ap.getFilterReadStatus() & 0x01) != 0) { 1680 where = " AND " + Sms.READ + "= 0"; 1681 } 1682 1683 if ((ap.getFilterReadStatus() & 0x02) != 0) { 1684 where = " AND " + Sms.READ + "= 1"; 1685 } 1686 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1687 if ((ap.getFilterReadStatus() & 0x01) != 0) { 1688 where = " AND " + Mms.READ + "= 0"; 1689 } 1690 1691 if ((ap.getFilterReadStatus() & 0x02) != 0) { 1692 where = " AND " + Mms.READ + "= 1"; 1693 } 1694 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || 1695 fi.mMsgType == FilterInfo.TYPE_IM) { 1696 if ((ap.getFilterReadStatus() & 0x01) != 0) { 1697 where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0"; 1698 } 1699 if ((ap.getFilterReadStatus() & 0x02) != 0) { 1700 where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 1"; 1701 } 1702 } 1703 } 1704 return where; 1705 } 1706 setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi)1707 private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) { 1708 String where = ""; 1709 1710 if ((ap.getFilterPeriodBegin() != -1)) { 1711 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1712 where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin(); 1713 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1714 where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L); 1715 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL|| 1716 fi.mMsgType == FilterInfo.TYPE_IM) { 1717 where = " AND " + BluetoothMapContract.MessageColumns.DATE + 1718 " >= " + (ap.getFilterPeriodBegin()); 1719 } 1720 } 1721 1722 if ((ap.getFilterPeriodEnd() != -1)) { 1723 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1724 where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd(); 1725 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1726 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L); 1727 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL|| 1728 fi.mMsgType == FilterInfo.TYPE_IM) { 1729 where += " AND " + BluetoothMapContract.MessageColumns.DATE + 1730 " < " + (ap.getFilterPeriodEnd()); 1731 } 1732 } 1733 return where; 1734 } setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi)1735 private String setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi) { 1736 String where = ""; 1737 if ((ap.getFilterLastActivityBegin() != -1)) { 1738 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1739 where = " AND " + Sms.DATE + " >= " + ap.getFilterLastActivityBegin(); 1740 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1741 where = " AND " + Mms.DATE + " >= " + (ap.getFilterLastActivityBegin() / 1000L); 1742 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL|| 1743 fi.mMsgType == FilterInfo.TYPE_IM ) { 1744 where = " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY + 1745 " >= " + (ap.getFilterPeriodBegin()); 1746 } 1747 } 1748 if ((ap.getFilterLastActivityEnd() != -1)) { 1749 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1750 where += " AND " + Sms.DATE + " < " + ap.getFilterLastActivityEnd(); 1751 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1752 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L); 1753 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||fi.mMsgType == FilterInfo.TYPE_IM) { 1754 where += " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY 1755 + " < " + (ap.getFilterLastActivityEnd()); 1756 } 1757 } 1758 return where; 1759 } 1760 1761 setWhereFilterOriginatorEmail(BluetoothMapAppParams ap)1762 private String setWhereFilterOriginatorEmail(BluetoothMapAppParams ap) { 1763 String where = ""; 1764 String orig = ap.getFilterOriginator(); 1765 1766 /* Be aware of wild cards in the beginning of string, may not be valid? */ 1767 if (orig != null && orig.length() > 0) { 1768 orig = orig.replace("*", "%"); 1769 where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST 1770 + " LIKE '%" + orig + "%'"; 1771 } 1772 return where; 1773 } 1774 setWhereFilterOriginatorIM(BluetoothMapAppParams ap)1775 private String setWhereFilterOriginatorIM(BluetoothMapAppParams ap) { 1776 String where = ""; 1777 String orig = ap.getFilterOriginator(); 1778 1779 /* Be aware of wild cards in the beginning of string, may not be valid? */ 1780 if (orig != null && orig.length() > 0) { 1781 orig = orig.replace("*", "%"); 1782 where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST 1783 + " LIKE '%" + orig + "%'"; 1784 } 1785 return where; 1786 } 1787 setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi)1788 private String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) { 1789 String where = ""; 1790 int pri = ap.getFilterPriority(); 1791 /*only MMS have priority info */ 1792 if(fi.mMsgType == FilterInfo.TYPE_MMS) 1793 { 1794 if(pri == 0x0002) 1795 { 1796 where += " AND " + Mms.PRIORITY + "<=" + 1797 Integer.toString(PduHeaders.PRIORITY_NORMAL); 1798 }else if(pri == 0x0001) { 1799 where += " AND " + Mms.PRIORITY + "=" + 1800 Integer.toString(PduHeaders.PRIORITY_HIGH); 1801 } 1802 } 1803 if(fi.mMsgType == FilterInfo.TYPE_EMAIL || 1804 fi.mMsgType == FilterInfo.TYPE_IM) 1805 { 1806 if(pri == 0x0002) 1807 { 1808 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "!=1"; 1809 }else if(pri == 0x0001) { 1810 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "=1"; 1811 } 1812 } 1813 // TODO: no priority filtering in IM 1814 return where; 1815 } 1816 setWhereFilterRecipientEmail(BluetoothMapAppParams ap)1817 private String setWhereFilterRecipientEmail(BluetoothMapAppParams ap) { 1818 String where = ""; 1819 String recip = ap.getFilterRecipient(); 1820 1821 /* Be aware of wild cards in the beginning of string, may not be valid? */ 1822 if (recip != null && recip.length() > 0) { 1823 recip = recip.replace("*", "%"); 1824 where = " AND (" 1825 + BluetoothMapContract.MessageColumns.TO_LIST + " LIKE '%" + recip + "%' OR " 1826 + BluetoothMapContract.MessageColumns.CC_LIST + " LIKE '%" + recip + "%' OR " 1827 + BluetoothMapContract.MessageColumns.BCC_LIST + " LIKE '%" + recip + "%' )"; 1828 } 1829 return where; 1830 } 1831 setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi)1832 private String setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi) { 1833 String where = ""; 1834 long id = -1; 1835 String msgHandle = ap.getFilterMsgHandleString(); 1836 if(msgHandle != null) { 1837 id = BluetoothMapUtils.getCpHandle(msgHandle); 1838 if(D)Log.d(TAG,"id: " + id); 1839 } 1840 if(id != -1) { 1841 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1842 where = " AND " + Sms._ID + " = " + id; 1843 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1844 where = " AND " + Mms._ID + " = " + id; 1845 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || 1846 fi.mMsgType == FilterInfo.TYPE_IM) { 1847 where = " AND " + BluetoothMapContract.MessageColumns._ID + " = " + id; 1848 } 1849 } 1850 return where; 1851 } 1852 setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi)1853 private String setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi) { 1854 String where = ""; 1855 long id = -1; 1856 String msgHandle = ap.getFilterConvoIdString(); 1857 if(msgHandle != null) { 1858 id = BluetoothMapUtils.getMsgHandleAsLong(msgHandle); 1859 if(D)Log.d(TAG,"id: " + id); 1860 } 1861 if(id > 0) { 1862 if (fi.mMsgType == FilterInfo.TYPE_SMS) { 1863 where = " AND " + Sms.THREAD_ID + " = " + id; 1864 } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { 1865 where = " AND " + Mms.THREAD_ID + " = " + id; 1866 } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || 1867 fi.mMsgType == FilterInfo.TYPE_IM) { 1868 where = " AND " + BluetoothMapContract.MessageColumns.THREAD_ID + " = " + id; 1869 } 1870 } 1871 1872 return where; 1873 } 1874 setWhereFilter(BluetoothMapFolderElement folderElement, FilterInfo fi, BluetoothMapAppParams ap)1875 private String setWhereFilter(BluetoothMapFolderElement folderElement, 1876 FilterInfo fi, BluetoothMapAppParams ap) { 1877 String where = ""; 1878 where += setWhereFilterFolderType(folderElement, fi); 1879 1880 String msgHandleWhere = setWhereFilterMessageHandle(ap, fi); 1881 /* if message handle filter is available, the other filters should be ignored */ 1882 if(msgHandleWhere.isEmpty()) { 1883 where += setWhereFilterReadStatus(ap, fi); 1884 where += setWhereFilterPriority(ap,fi); 1885 where += setWhereFilterPeriod(ap, fi); 1886 if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { 1887 where += setWhereFilterOriginatorEmail(ap); 1888 where += setWhereFilterRecipientEmail(ap); 1889 } 1890 if (fi.mMsgType == FilterInfo.TYPE_IM) { 1891 where += setWhereFilterOriginatorIM(ap); 1892 // TODO: set 'where' filer recipient? 1893 } 1894 where += setWhereFilterThreadId(ap, fi); 1895 } else { 1896 where += msgHandleWhere; 1897 } 1898 1899 return where; 1900 } 1901 1902 1903 /* Used only for SMS/MMS */ setConvoWhereFilterSmsMms(StringBuilder selection, ArrayList<String> selectionArgs, FilterInfo fi, BluetoothMapAppParams ap)1904 private void setConvoWhereFilterSmsMms(StringBuilder selection, ArrayList<String> selectionArgs, 1905 FilterInfo fi, BluetoothMapAppParams ap) { 1906 1907 if (smsSelected(fi, ap) || mmsSelected(ap)) { 1908 1909 // Filter Read Status 1910 if(ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1911 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_UNREAD_ONLY) != 0) { 1912 selection.append(" AND ").append(Threads.READ).append(" = 0"); 1913 } 1914 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_READ_ONLY) != 0) { 1915 selection.append(" AND ").append(Threads.READ).append(" = 1"); 1916 } 1917 } 1918 1919 // Filter time 1920 if ((ap.getFilterLastActivityBegin() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)){ 1921 selection.append(" AND ").append(Threads.DATE).append(" >= ") 1922 .append(ap.getFilterLastActivityBegin()); 1923 } 1924 if ((ap.getFilterLastActivityEnd() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) { 1925 selection.append(" AND ").append(Threads.DATE).append(" <= ") 1926 .append(ap.getFilterLastActivityEnd()); 1927 } 1928 1929 // Filter ConvoId 1930 long convoId = -1; 1931 if(ap.getFilterConvoId() != null) { 1932 convoId = ap.getFilterConvoId().getLeastSignificantBits(); 1933 } 1934 if(convoId > 0) { 1935 selection.append(" AND ").append(Threads._ID).append(" = ") 1936 .append(Long.toString(convoId)); 1937 } 1938 } 1939 } 1940 1941 1942 1943 /** 1944 * Determine from application parameter if sms should be included. 1945 * The filter mask is set for message types not selected 1946 * @param fi 1947 * @param ap 1948 * @return boolean true if sms is selected, false if not 1949 */ smsSelected(FilterInfo fi, BluetoothMapAppParams ap)1950 private boolean smsSelected(FilterInfo fi, BluetoothMapAppParams ap) { 1951 int msgType = ap.getFilterMessageType(); 1952 int phoneType = fi.mPhoneType; 1953 1954 if (D) Log.d(TAG, "smsSelected msgType: " + msgType); 1955 1956 if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 1957 return true; 1958 1959 if ((msgType & (BluetoothMapAppParams.FILTER_NO_SMS_CDMA 1960 |BluetoothMapAppParams.FILTER_NO_SMS_GSM)) == 0) 1961 return true; 1962 1963 if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0) 1964 && (phoneType == TelephonyManager.PHONE_TYPE_GSM)) 1965 return true; 1966 1967 if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0) 1968 && (phoneType == TelephonyManager.PHONE_TYPE_CDMA)) 1969 return true; 1970 1971 return false; 1972 } 1973 1974 /** 1975 * Determine from application parameter if mms should be included. 1976 * The filter mask is set for message types not selected 1977 * @param fi 1978 * @param ap 1979 * @return boolean true if mms is selected, false if not 1980 */ mmsSelected(BluetoothMapAppParams ap)1981 private boolean mmsSelected(BluetoothMapAppParams ap) { 1982 int msgType = ap.getFilterMessageType(); 1983 1984 if (D) Log.d(TAG, "mmsSelected msgType: " + msgType); 1985 1986 if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 1987 return true; 1988 1989 if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0) 1990 return true; 1991 1992 return false; 1993 } 1994 1995 /** 1996 * Determine from application parameter if email should be included. 1997 * The filter mask is set for message types not selected 1998 * @param fi 1999 * @param ap 2000 * @return boolean true if email is selected, false if not 2001 */ emailSelected(BluetoothMapAppParams ap)2002 private boolean emailSelected(BluetoothMapAppParams ap) { 2003 int msgType = ap.getFilterMessageType(); 2004 2005 if (D) Log.d(TAG, "emailSelected msgType: " + msgType); 2006 2007 if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 2008 return true; 2009 2010 if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0) 2011 return true; 2012 2013 return false; 2014 } 2015 2016 /** 2017 * Determine from application parameter if IM should be included. 2018 * The filter mask is set for message types not selected 2019 * @param fi 2020 * @param ap 2021 * @return boolean true if im is selected, false if not 2022 */ imSelected(BluetoothMapAppParams ap)2023 private boolean imSelected(BluetoothMapAppParams ap) { 2024 int msgType = ap.getFilterMessageType(); 2025 2026 if (D) Log.d(TAG, "imSelected msgType: " + msgType); 2027 2028 if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 2029 return true; 2030 2031 if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0) 2032 return true; 2033 2034 return false; 2035 } 2036 setFilterInfo(FilterInfo fi)2037 private void setFilterInfo(FilterInfo fi) { 2038 TelephonyManager tm = 2039 (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); 2040 if (tm != null) { 2041 fi.mPhoneType = tm.getPhoneType(); 2042 fi.mPhoneNum = tm.getLine1Number(); 2043 fi.mPhoneAlphaTag = tm.getLine1AlphaTag(); 2044 if (D) Log.d(TAG, "phone type = " + fi.mPhoneType + 2045 " phone num = " + fi.mPhoneNum + 2046 " phone alpha tag = " + fi.mPhoneAlphaTag); 2047 } 2048 } 2049 2050 /** 2051 * Get a listing of message in folder after applying filter. 2052 * @param folder Must contain a valid folder string != null 2053 * @param ap Parameters specifying message content and filters 2054 * @return Listing object containing requested messages 2055 */ msgListing(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2056 public BluetoothMapMessageListing msgListing(BluetoothMapFolderElement folderElement, 2057 BluetoothMapAppParams ap) { 2058 if (D) Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType() ); 2059 2060 BluetoothMapMessageListing bmList = new BluetoothMapMessageListing(); 2061 2062 /* We overwrite the parameter mask here if it is 0 or not present, as this 2063 * should cause all parameters to be included in the message list. */ 2064 if(ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER || 2065 ap.getParameterMask() == 0) { 2066 ap.setParameterMask(PARAMETER_MASK_DEFAULT); 2067 if (V) Log.v(TAG, "msgListing(): appParameterMask is zero or not present, " + 2068 "changing to default: " + ap.getParameterMask()); 2069 } 2070 if (V) Log.v(TAG, "folderElement hasSmsMmsContent = " + folderElement.hasSmsMmsContent() + 2071 " folderElement.hasEmailContent = " + folderElement.hasEmailContent() + 2072 " folderElement.hasImContent = " + folderElement.hasImContent()); 2073 2074 /* Cache some info used throughout filtering */ 2075 FilterInfo fi = new FilterInfo(); 2076 setFilterInfo(fi); 2077 Cursor smsCursor = null; 2078 Cursor mmsCursor = null; 2079 Cursor emailCursor = null; 2080 Cursor imCursor = null; 2081 String limit = ""; 2082 int countNum = ap.getMaxListCount(); 2083 int offsetNum = ap.getStartOffset(); 2084 if(ap.getMaxListCount()>0){ 2085 limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset()); 2086 } 2087 try{ 2088 if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { 2089 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL| 2090 BluetoothMapAppParams.FILTER_NO_MMS| 2091 BluetoothMapAppParams.FILTER_NO_SMS_GSM| 2092 BluetoothMapAppParams.FILTER_NO_IM)|| 2093 ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL| 2094 BluetoothMapAppParams.FILTER_NO_MMS| 2095 BluetoothMapAppParams.FILTER_NO_SMS_CDMA| 2096 BluetoothMapAppParams.FILTER_NO_IM)){ 2097 //set real limit and offset if only this type is used 2098 // (only if offset/limit is used) 2099 limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset(); 2100 if(D) Log.d(TAG, "SMS Limit => "+limit); 2101 offsetNum = 0; 2102 } 2103 fi.mMsgType = FilterInfo.TYPE_SMS; 2104 if(ap.getFilterPriority() != 1){ /*SMS cannot have high priority*/ 2105 String where = setWhereFilter(folderElement, fi, ap); 2106 if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); 2107 smsCursor = mResolver.query(Sms.CONTENT_URI, 2108 SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit); 2109 if (smsCursor != null) { 2110 BluetoothMapMessageListingElement e = null; 2111 // store column index so we dont have to look them up anymore (optimization) 2112 if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages."); 2113 fi.setSmsColumns(smsCursor); 2114 while (smsCursor.moveToNext()) { 2115 if (matchAddresses(smsCursor, fi, ap)) { 2116 if(V) BluetoothMapUtils.printCursor(smsCursor); 2117 e = element(smsCursor, fi, ap); 2118 bmList.add(e); 2119 } 2120 } 2121 } 2122 } 2123 } 2124 2125 if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { 2126 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL| 2127 BluetoothMapAppParams.FILTER_NO_SMS_CDMA| 2128 BluetoothMapAppParams.FILTER_NO_SMS_GSM| 2129 BluetoothMapAppParams.FILTER_NO_IM)){ 2130 //set real limit and offset if only this type is used 2131 //(only if offset/limit is used) 2132 limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset(); 2133 if(D) Log.d(TAG, "MMS Limit => "+limit); 2134 offsetNum = 0; 2135 } 2136 fi.mMsgType = FilterInfo.TYPE_MMS; 2137 String where = setWhereFilter(folderElement, fi, ap); 2138 where += " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE; 2139 if(!where.isEmpty()) { 2140 if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); 2141 mmsCursor = mResolver.query(Mms.CONTENT_URI, 2142 MMS_PROJECTION, where, null, Mms.DATE + " DESC" + limit); 2143 if (mmsCursor != null) { 2144 BluetoothMapMessageListingElement e = null; 2145 // store column index so we dont have to look them up anymore (optimization) 2146 fi.setMmsColumns(mmsCursor); 2147 if(D) Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages."); 2148 while (mmsCursor.moveToNext()) { 2149 if (matchAddresses(mmsCursor, fi, ap)) { 2150 if(V) BluetoothMapUtils.printCursor(mmsCursor); 2151 e = element(mmsCursor, fi, ap); 2152 bmList.add(e); 2153 } 2154 } 2155 } 2156 } 2157 } 2158 2159 if (emailSelected(ap) && folderElement.hasEmailContent()) { 2160 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS| 2161 BluetoothMapAppParams.FILTER_NO_SMS_CDMA| 2162 BluetoothMapAppParams.FILTER_NO_SMS_GSM| 2163 BluetoothMapAppParams.FILTER_NO_IM)){ 2164 //set real limit and offset if only this type is used 2165 //(only if offset/limit is used) 2166 limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset(); 2167 if(D) Log.d(TAG, "Email Limit => "+limit); 2168 offsetNum = 0; 2169 } 2170 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2171 String where = setWhereFilter(folderElement, fi, ap); 2172 2173 if(!where.isEmpty()) { 2174 if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); 2175 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2176 emailCursor = mResolver.query(contentUri, 2177 BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null, 2178 BluetoothMapContract.MessageColumns.DATE + " DESC" + limit); 2179 if (emailCursor != null) { 2180 BluetoothMapMessageListingElement e = null; 2181 // store column index so we dont have to look them up anymore (optimization) 2182 fi.setEmailMessageColumns(emailCursor); 2183 int cnt = 0; 2184 if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages."); 2185 while (emailCursor.moveToNext()) { 2186 if(V) BluetoothMapUtils.printCursor(emailCursor); 2187 e = element(emailCursor, fi, ap); 2188 bmList.add(e); 2189 } 2190 // emailCursor.close(); 2191 } 2192 } 2193 } 2194 2195 if (imSelected(ap) && folderElement.hasImContent()) { 2196 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS| 2197 BluetoothMapAppParams.FILTER_NO_SMS_CDMA| 2198 BluetoothMapAppParams.FILTER_NO_SMS_GSM| 2199 BluetoothMapAppParams.FILTER_NO_EMAIL)){ 2200 //set real limit and offset if only this type is used 2201 //(only if offset/limit is used) 2202 limit = " LIMIT " + ap.getMaxListCount() + " OFFSET "+ ap.getStartOffset(); 2203 if(D) Log.d(TAG, "IM Limit => "+limit); 2204 offsetNum = 0; 2205 } 2206 fi.mMsgType = FilterInfo.TYPE_IM; 2207 String where = setWhereFilter(folderElement, fi, ap); 2208 if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); 2209 2210 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2211 imCursor = mResolver.query(contentUri, 2212 BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, 2213 where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit); 2214 if (imCursor != null) { 2215 BluetoothMapMessageListingElement e = null; 2216 // store column index so we dont have to look them up anymore (optimization) 2217 fi.setImMessageColumns(imCursor); 2218 if (D) Log.d(TAG, "Found " + imCursor.getCount() + " im messages."); 2219 while (imCursor.moveToNext()) { 2220 if (V) BluetoothMapUtils.printCursor(imCursor); 2221 e = element(imCursor, fi, ap); 2222 bmList.add(e); 2223 } 2224 } 2225 } 2226 2227 /* Enable this if post sorting and segmenting needed */ 2228 bmList.sort(); 2229 bmList.segment(ap.getMaxListCount(), offsetNum); 2230 List<BluetoothMapMessageListingElement> list = bmList.getList(); 2231 int listSize = list.size(); 2232 Cursor tmpCursor = null; 2233 for(int x=0;x<listSize;x++){ 2234 BluetoothMapMessageListingElement ele = list.get(x); 2235 /* If OBEX "GET" request header includes "ParameterMask" with 'Type' NOT set, 2236 * then ele.getType() returns "null" even for a valid cursor. 2237 * Avoid NullPointerException in equals() check when 'mType' value is "null" */ 2238 TYPE tmpType = ele.getType(); 2239 if (smsCursor!= null && 2240 ((TYPE.SMS_GSM).equals(tmpType) || (TYPE.SMS_CDMA).equals(tmpType))) { 2241 tmpCursor = smsCursor; 2242 fi.mMsgType = FilterInfo.TYPE_SMS; 2243 } else if(mmsCursor != null && (TYPE.MMS).equals(tmpType)) { 2244 tmpCursor = mmsCursor; 2245 fi.mMsgType = FilterInfo.TYPE_MMS; 2246 } else if(emailCursor != null && ((TYPE.EMAIL).equals(tmpType))) { 2247 tmpCursor = emailCursor; 2248 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2249 } else if(imCursor != null && ((TYPE.IM).equals(tmpType))) { 2250 tmpCursor = imCursor; 2251 fi.mMsgType = FilterInfo.TYPE_IM; 2252 } 2253 if(tmpCursor != null){ 2254 tmpCursor.moveToPosition(ele.getCursorIndex()); 2255 setSenderAddressing(ele, tmpCursor, fi, ap); 2256 setSenderName(ele, tmpCursor, fi, ap); 2257 setRecipientAddressing(ele, tmpCursor, fi, ap); 2258 setRecipientName(ele, tmpCursor, fi, ap); 2259 setSubject(ele, tmpCursor, fi, ap); 2260 setSize(ele, tmpCursor, fi, ap); 2261 setText(ele, tmpCursor, fi, ap); 2262 setPriority(ele, tmpCursor, fi, ap); 2263 setSent(ele, tmpCursor, fi, ap); 2264 setProtected(ele, tmpCursor, fi, ap); 2265 setReceptionStatus(ele, tmpCursor, fi, ap); 2266 setAttachment(ele, tmpCursor, fi, ap); 2267 2268 if(mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10 ){ 2269 setDeliveryStatus(ele, tmpCursor, fi, ap); 2270 setThreadId(ele, tmpCursor, fi, ap); 2271 setThreadName(ele, tmpCursor, fi, ap); 2272 setFolderType(ele, tmpCursor, fi, ap); 2273 } 2274 } 2275 } 2276 } finally { 2277 if(emailCursor != null)emailCursor.close(); 2278 if(smsCursor != null)smsCursor.close(); 2279 if(mmsCursor != null)mmsCursor.close(); 2280 if(imCursor != null)imCursor.close(); 2281 } 2282 2283 2284 if(D)Log.d(TAG, "messagelisting end"); 2285 return bmList; 2286 } 2287 2288 /** 2289 * Get the size of the message listing 2290 * @param folder Must contain a valid folder string != null 2291 * @param ap Parameters specifying message content and filters 2292 * @return Integer equal to message listing size 2293 */ msgListingSize(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2294 public int msgListingSize(BluetoothMapFolderElement folderElement, 2295 BluetoothMapAppParams ap) { 2296 if (D) Log.d(TAG, "msgListingSize: folder = " + folderElement.getName()); 2297 int cnt = 0; 2298 2299 /* Cache some info used throughout filtering */ 2300 FilterInfo fi = new FilterInfo(); 2301 setFilterInfo(fi); 2302 2303 if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { 2304 fi.mMsgType = FilterInfo.TYPE_SMS; 2305 String where = setWhereFilter(folderElement, fi, ap); 2306 Cursor c = mResolver.query(Sms.CONTENT_URI, 2307 SMS_PROJECTION, where, null, Sms.DATE + " DESC"); 2308 try { 2309 if (c != null) { 2310 cnt = c.getCount(); 2311 } 2312 } finally { 2313 if (c != null) c.close(); 2314 } 2315 } 2316 2317 if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { 2318 fi.mMsgType = FilterInfo.TYPE_MMS; 2319 String where = setWhereFilter(folderElement, fi, ap); 2320 Cursor c = mResolver.query(Mms.CONTENT_URI, 2321 MMS_PROJECTION, where, null, Mms.DATE + " DESC"); 2322 try { 2323 if (c != null) { 2324 cnt += c.getCount(); 2325 } 2326 } finally { 2327 if (c != null) c.close(); 2328 } 2329 } 2330 2331 if (emailSelected(ap) && folderElement.hasEmailContent()) { 2332 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2333 String where = setWhereFilter(folderElement, fi, ap); 2334 if(!where.isEmpty()) { 2335 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2336 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, 2337 where, null, BluetoothMapContract.MessageColumns.DATE + " DESC"); 2338 try { 2339 if (c != null) { 2340 cnt += c.getCount(); 2341 } 2342 } finally { 2343 if (c != null) c.close(); 2344 } 2345 } 2346 } 2347 2348 if (imSelected(ap) && folderElement.hasImContent()) { 2349 fi.mMsgType = FilterInfo.TYPE_IM; 2350 String where = setWhereFilter(folderElement, fi, ap); 2351 if(!where.isEmpty()) { 2352 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2353 Cursor c = mResolver.query(contentUri, 2354 BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, 2355 where, null, BluetoothMapContract.MessageColumns.DATE + " DESC"); 2356 try { 2357 if (c != null) { 2358 cnt += c.getCount(); 2359 } 2360 } finally { 2361 if (c != null) c.close(); 2362 } 2363 } 2364 } 2365 2366 if (D) Log.d(TAG, "msgListingSize: size = " + cnt); 2367 return cnt; 2368 } 2369 2370 /** 2371 * Return true if there are unread messages in the requested list of messages 2372 * @param folder folder where the message listing should come from 2373 * @param ap application parameter object 2374 * @return true if unread messages are in the list, else false 2375 */ msgListingHasUnread(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2376 public boolean msgListingHasUnread(BluetoothMapFolderElement folderElement, 2377 BluetoothMapAppParams ap) { 2378 if (D) Log.d(TAG, "msgListingHasUnread: folder = " + folderElement.getName()); 2379 int cnt = 0; 2380 2381 /* Cache some info used throughout filtering */ 2382 FilterInfo fi = new FilterInfo(); 2383 setFilterInfo(fi); 2384 2385 if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { 2386 fi.mMsgType = FilterInfo.TYPE_SMS; 2387 String where = setWhereFilterFolderType(folderElement, fi); 2388 where += " AND " + Sms.READ + "=0 "; 2389 where += setWhereFilterPeriod(ap, fi); 2390 Cursor c = mResolver.query(Sms.CONTENT_URI, 2391 SMS_PROJECTION, where, null, Sms.DATE + " DESC"); 2392 try { 2393 if (c != null) { 2394 cnt = c.getCount(); 2395 } 2396 } finally { 2397 if (c != null) c.close(); 2398 } 2399 } 2400 2401 if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { 2402 fi.mMsgType = FilterInfo.TYPE_MMS; 2403 String where = setWhereFilterFolderType(folderElement, fi); 2404 where += " AND " + Mms.READ + "=0 "; 2405 where += setWhereFilterPeriod(ap, fi); 2406 Cursor c = mResolver.query(Mms.CONTENT_URI, 2407 MMS_PROJECTION, where, null, Sms.DATE + " DESC"); 2408 try { 2409 if (c != null) { 2410 cnt += c.getCount(); 2411 } 2412 } finally { 2413 if (c != null) c.close(); 2414 } 2415 } 2416 2417 2418 if (emailSelected(ap) && folderElement.getFolderId() != -1) { 2419 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2420 String where = setWhereFilterFolderType(folderElement, fi); 2421 if(!where.isEmpty()) { 2422 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 "; 2423 where += setWhereFilterPeriod(ap, fi); 2424 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2425 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, 2426 where, null, BluetoothMapContract.MessageColumns.DATE + " DESC"); 2427 try { 2428 if (c != null) { 2429 cnt += c.getCount(); 2430 } 2431 } finally { 2432 if (c != null) c.close(); 2433 } 2434 } 2435 } 2436 2437 if (imSelected(ap) && folderElement.hasImContent()) { 2438 fi.mMsgType = FilterInfo.TYPE_IM; 2439 String where = setWhereFilter(folderElement, fi, ap); 2440 if(!where.isEmpty()) { 2441 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 "; 2442 where += setWhereFilterPeriod(ap, fi); 2443 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 2444 Cursor c = mResolver.query(contentUri, 2445 BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, 2446 where, null, BluetoothMapContract.MessageColumns.DATE + " DESC"); 2447 try { 2448 if (c != null) { 2449 cnt += c.getCount(); 2450 } 2451 } finally { 2452 if (c != null) c.close(); 2453 } 2454 } 2455 } 2456 2457 if (D) Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt); 2458 return (cnt>0)?true:false; 2459 } 2460 2461 /** 2462 * Build the conversation listing. 2463 * @param ap The Application Parameters 2464 * @param sizeOnly TRUE: don't populate the list members, only build the list to get the size. 2465 * @return 2466 */ convoListing(BluetoothMapAppParams ap, boolean sizeOnly)2467 public BluetoothMapConvoListing convoListing(BluetoothMapAppParams ap, boolean sizeOnly) { 2468 2469 if (D) Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType() ); 2470 BluetoothMapConvoListing convoList = new BluetoothMapConvoListing(); 2471 2472 /* We overwrite the parameter mask here if it is 0 or not present, as this 2473 * should cause all parameters to be included in the message list. */ 2474 if(ap.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER || 2475 ap.getConvoParameterMask() == 0) { 2476 ap.setConvoParameterMask(CONVO_PARAMETER_MASK_DEFAULT); 2477 if (D) Log.v(TAG, "convoListing(): appParameterMask is zero or not present, " + 2478 "changing to default: " + ap.getConvoParameterMask()); 2479 } 2480 2481 /* Possible filters: 2482 * - Recipient name (contacts DB) or id (for SMS/MMS this is the thread-id contact-id) 2483 * - Activity start/begin 2484 * - Read status 2485 * - Thread_id 2486 * The strategy for SMS/MMS 2487 * With no filter on name - use limit and offset. 2488 * With a filter on name - build the complete list of conversations and create a filter 2489 * mechanism 2490 * 2491 * The strategy for IM: 2492 * Join the conversation table with the contacts table in a way that makes it possible to 2493 * get the data needed in a single query. 2494 * Manually handle limit/offset 2495 * */ 2496 2497 /* Cache some info used throughout filtering */ 2498 FilterInfo fi = new FilterInfo(); 2499 setFilterInfo(fi); 2500 Cursor smsMmsCursor = null; 2501 Cursor imEmailCursor = null; 2502 int offsetNum; 2503 if(sizeOnly) { 2504 offsetNum = 0; 2505 } else { 2506 offsetNum = ap.getStartOffset(); 2507 } 2508 // Inverse meaning - hence a 1 is include. 2509 int msgTypesInclude = ((~ap.getFilterMessageType()) 2510 & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK); 2511 int maxThreads = ap.getMaxListCount()+ap.getStartOffset(); 2512 2513 2514 try { 2515 if (smsSelected(fi, ap) || mmsSelected(ap)) { 2516 String limit = ""; 2517 if((sizeOnly == false) && (ap.getMaxListCount()>0) && 2518 (ap.getFilterRecipient()==null)){ 2519 /* We can only use limit if we do not have a contacts filter */ 2520 limit=" LIMIT " + maxThreads; 2521 } 2522 StringBuilder sortOrder = new StringBuilder(Threads.DATE + " DESC"); 2523 if((sizeOnly == false) && 2524 ((msgTypesInclude & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM | 2525 BluetoothMapAppParams.FILTER_NO_SMS_CDMA) | 2526 BluetoothMapAppParams.FILTER_NO_MMS) == 0) 2527 && ap.getFilterRecipient() == null){ 2528 // SMS/MMS messages only and no recipient filter - use optimization. 2529 limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset(); 2530 if(D) Log.d(TAG, "SMS Limit => "+limit); 2531 offsetNum = 0; 2532 } 2533 StringBuilder selection = new StringBuilder(120); // This covers most cases 2534 ArrayList<String> selectionArgs = new ArrayList<String>(12); // Covers all cases 2535 selection.append("1=1 "); // just to simplify building the where-clause 2536 setConvoWhereFilterSmsMms(selection, selectionArgs, fi, ap); 2537 String[] args = null; 2538 if(selectionArgs.size() > 0) { 2539 args = new String[selectionArgs.size()]; 2540 selectionArgs.toArray(args); 2541 } 2542 Uri uri = Threads.CONTENT_URI.buildUpon() 2543 .appendQueryParameter("simple", "true").build(); 2544 sortOrder.append(limit); 2545 if(D) Log.d(TAG, "Query using selection: " + selection.toString() + 2546 " - sortOrder: " + sortOrder.toString()); 2547 // TODO: Optimize: Reduce projection based on convo parameter mask 2548 smsMmsCursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(), 2549 args, sortOrder.toString()); 2550 if (smsMmsCursor != null) { 2551 // store column index so we don't have to look them up anymore (optimization) 2552 if(D) Log.d(TAG, "Found " + smsMmsCursor.getCount() 2553 + " sms/mms conversations."); 2554 BluetoothMapConvoListingElement convoElement = null; 2555 smsMmsCursor.moveToPosition(-1); 2556 if(ap.getFilterRecipient() == null) { 2557 int count = 0; 2558 // We have no Recipient filter, add contacts after the list is reduced 2559 while (smsMmsCursor.moveToNext()) { 2560 convoElement = createConvoElement(smsMmsCursor, fi, ap); 2561 convoList.add(convoElement); 2562 count++; 2563 if(sizeOnly == false && count >= maxThreads) { 2564 break; 2565 } 2566 } 2567 } else { 2568 // We must be able to filter on recipient, add contacts now 2569 SmsMmsContacts contacts = new SmsMmsContacts(); 2570 while (smsMmsCursor.moveToNext()) { 2571 int count = 0; 2572 convoElement = createConvoElement(smsMmsCursor, fi, ap); 2573 String idsStr = 2574 smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); 2575 // Add elements only if we do find a contact - if not we cannot apply 2576 // the filter, hence the item is irrelevant 2577 // TODO: Perhaps the spec. should be changes to be able to search on 2578 // phone number as well? 2579 if(addSmsMmsContacts(convoElement, contacts, idsStr, 2580 ap.getFilterRecipient(), ap)) { 2581 convoList.add(convoElement); 2582 if(sizeOnly == false && count >= maxThreads) { 2583 break; 2584 } 2585 } 2586 } 2587 } 2588 } 2589 } 2590 2591 if (emailSelected(ap) || imSelected(ap)) { 2592 int count = 0; 2593 if(emailSelected(ap)) { 2594 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2595 } else if(imSelected(ap)) { 2596 fi.mMsgType = FilterInfo.TYPE_IM; 2597 } 2598 if (D) Log.d(TAG, "msgType: " + fi.mMsgType); 2599 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION); 2600 2601 contentUri = appendConvoListQueryParameters(ap, contentUri); 2602 if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString()); 2603 // TODO: Optimize: Reduce projection based on convo parameter mask 2604 imEmailCursor = mResolver.query(contentUri, 2605 BluetoothMapContract.BT_CONVERSATION_PROJECTION, 2606 null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY 2607 + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID 2608 + " ASC"); 2609 if (imEmailCursor != null) { 2610 BluetoothMapConvoListingElement e = null; 2611 // store column index so we don't have to look them up anymore (optimization) 2612 // Here we rely on only a single account-based message type for each MAS. 2613 fi.setEmailImConvoColumns(imEmailCursor); 2614 boolean isValid = imEmailCursor.moveToNext(); 2615 if(D) Log.d(TAG, "Found " + imEmailCursor.getCount() 2616 + " EMAIL/IM conversations. isValid = " + isValid); 2617 while (isValid && ((sizeOnly == true) || (count < maxThreads))) { 2618 long threadId = imEmailCursor.getLong(fi.mConvoColConvoId); 2619 long nextThreadId; 2620 count ++; 2621 e = createConvoElement(imEmailCursor, fi, ap); 2622 convoList.add(e); 2623 2624 do { 2625 nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId); 2626 if(V) Log.i(TAG, " threadId = " + threadId + " newThreadId = " + 2627 nextThreadId); 2628 // TODO: This seems rather inefficient in the case where we do not need 2629 // to reduce the list. 2630 } while ((nextThreadId == threadId) && 2631 (isValid = imEmailCursor.moveToNext() == true)); 2632 } 2633 } 2634 } 2635 2636 if(D) Log.d(TAG, "Done adding conversations - list size:" + 2637 convoList.getCount()); 2638 2639 // If sizeOnly - we are all done here - return the list as is - no need to populate the 2640 // list. 2641 if(sizeOnly) { 2642 return convoList; 2643 } 2644 2645 /* Enable this if post sorting and segmenting needed */ 2646 /* This is too early */ 2647 convoList.sort(); 2648 convoList.segment(ap.getMaxListCount(), offsetNum); 2649 List<BluetoothMapConvoListingElement> list = convoList.getList(); 2650 int listSize = list.size(); 2651 if(V) Log.i(TAG, "List Size:" + listSize); 2652 Cursor tmpCursor = null; 2653 SmsMmsContacts contacts = new SmsMmsContacts(); 2654 for(int x=0;x<listSize;x++){ 2655 BluetoothMapConvoListingElement ele = list.get(x); 2656 TYPE type = ele.getType(); 2657 switch(type) { 2658 case SMS_CDMA: 2659 case SMS_GSM: 2660 case MMS: { 2661 tmpCursor = null; // SMS/MMS needs special treatment 2662 if(smsMmsCursor != null) { 2663 populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts); 2664 } 2665 if(D) fi.mMsgType = FilterInfo.TYPE_IM; 2666 break; 2667 } 2668 case EMAIL: 2669 tmpCursor = imEmailCursor; 2670 fi.mMsgType = FilterInfo.TYPE_EMAIL; 2671 break; 2672 case IM: 2673 tmpCursor = imEmailCursor; 2674 fi.mMsgType = FilterInfo.TYPE_IM; 2675 break; 2676 default: 2677 tmpCursor = null; 2678 break; 2679 } 2680 2681 if(D) Log.d(TAG, "Working on cursor of type " + fi.mMsgType); 2682 2683 if(tmpCursor != null){ 2684 populateImEmailConvoElement(ele, tmpCursor, ap, fi); 2685 }else { 2686 // No, it will be for SMS/MMS at the moment 2687 if(D) Log.d(TAG, "tmpCursor is Null - something is wrong - or the message is" + 2688 " of type SMS/MMS"); 2689 } 2690 } 2691 } finally { 2692 if(imEmailCursor != null)imEmailCursor.close(); 2693 if(smsMmsCursor != null)smsMmsCursor.close(); 2694 if(D)Log.d(TAG, "conversation end"); 2695 } 2696 return convoList; 2697 } 2698 2699 2700 /** 2701 * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a 2702 * new ConvoListVersinoCounter in mSmsMmsConvoListVersion 2703 * @return 2704 */ 2705 /* package */ refreshSmsMmsConvoVersions()2706 boolean refreshSmsMmsConvoVersions() { 2707 boolean listChangeDetected = false; 2708 Cursor cursor = null; 2709 Uri uri = Threads.CONTENT_URI.buildUpon() 2710 .appendQueryParameter("simple", "true").build(); 2711 cursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null, 2712 null, Threads.DATE + " DESC"); 2713 try { 2714 if (cursor != null) { 2715 // store column index so we don't have to look them up anymore (optimization) 2716 if(D) Log.d(TAG, "Found " + cursor.getCount() 2717 + " sms/mms conversations."); 2718 BluetoothMapConvoListingElement convoElement = null; 2719 cursor.moveToPosition(-1); 2720 synchronized (getSmsMmsConvoList()) { 2721 int size = Math.max(getSmsMmsConvoList().size(), cursor.getCount()); 2722 HashMap<Long,BluetoothMapConvoListingElement> newList = 2723 new HashMap<Long,BluetoothMapConvoListingElement>(size); 2724 while (cursor.moveToNext()) { 2725 // TODO: Extract to function, that can be called at listing, which returns 2726 // the versionCounter(existing or new). 2727 boolean convoChanged = false; 2728 Long id = cursor.getLong(MMS_SMS_THREAD_COL_ID); 2729 convoElement = getSmsMmsConvoList().remove(id); 2730 if(convoElement == null) { 2731 // New conversation added 2732 convoElement = new BluetoothMapConvoListingElement(); 2733 convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id); 2734 listChangeDetected = true; 2735 convoElement.setVersionCounter(0); 2736 } 2737 // Currently we only need to compare name, last_activity and read_status, and 2738 // name is not used for SMS/MMS. 2739 // msg delete will be handled by update folderVersionCounter(). 2740 long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE); 2741 boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ? 2742 true : false; 2743 2744 if(last_activity != convoElement.getLastActivity()) { 2745 convoChanged = true; 2746 convoElement.setLastActivity(last_activity); 2747 } 2748 2749 if(read != convoElement.getReadBool()) { 2750 convoChanged = true; 2751 convoElement.setRead(read, false); 2752 } 2753 2754 String idsStr = cursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); 2755 if(!idsStr.equals(convoElement.getSmsMmsContacts())) { 2756 // This should not trigger a change in conversationVersionCounter only the 2757 // ConvoListVersionCounter. 2758 listChangeDetected = true; 2759 convoElement.setSmsMmsContacts(idsStr); 2760 } 2761 2762 if(convoChanged) { 2763 listChangeDetected = true; 2764 convoElement.incrementVersionCounter(); 2765 } 2766 newList.put(id, convoElement); 2767 } 2768 // If we still have items on the old list, something was deleted 2769 if(getSmsMmsConvoList().size() != 0) { 2770 listChangeDetected = true; 2771 } 2772 setSmsMmsConvoList(newList); 2773 } 2774 2775 if(listChangeDetected) { 2776 mMasInstance.updateSmsMmsConvoListVersionCounter(); 2777 } 2778 } 2779 } finally { 2780 if(cursor != null) { 2781 cursor.close(); 2782 } 2783 } 2784 return listChangeDetected; 2785 } 2786 2787 /** 2788 * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a 2789 * new ConvoListVersinoCounter in mSmsMmsConvoListVersion 2790 * @return 2791 */ 2792 /* package */ refreshImEmailConvoVersions()2793 boolean refreshImEmailConvoVersions() { 2794 boolean listChangeDetected = false; 2795 FilterInfo fi = new FilterInfo(); 2796 2797 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION); 2798 2799 if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString()); 2800 Cursor imEmailCursor = mResolver.query(contentUri, 2801 CONVO_VERSION_PROJECTION, 2802 null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY 2803 + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID 2804 + " ASC"); 2805 try { 2806 if (imEmailCursor != null) { 2807 BluetoothMapConvoListingElement convoElement = null; 2808 // store column index so we don't have to look them up anymore (optimization) 2809 // Here we rely on only a single account-based message type for each MAS. 2810 fi.setEmailImConvoColumns(imEmailCursor); 2811 boolean isValid = imEmailCursor.moveToNext(); 2812 if(V) Log.d(TAG, "Found " + imEmailCursor.getCount() 2813 + " EMAIL/IM conversations. isValid = " + isValid); 2814 synchronized (getImEmailConvoList()) { 2815 int size = Math.max(getImEmailConvoList().size(), imEmailCursor.getCount()); 2816 boolean convoChanged = false; 2817 HashMap<Long,BluetoothMapConvoListingElement> newList = 2818 new HashMap<Long,BluetoothMapConvoListingElement>(size); 2819 while (isValid) { 2820 long id = imEmailCursor.getLong(fi.mConvoColConvoId); 2821 long nextThreadId; 2822 convoElement = getImEmailConvoList().remove(id); 2823 if(convoElement == null) { 2824 // New conversation added 2825 convoElement = new BluetoothMapConvoListingElement(); 2826 convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id); 2827 listChangeDetected = true; 2828 convoElement.setVersionCounter(0); 2829 } 2830 String name = imEmailCursor.getString(fi.mConvoColName); 2831 String summary = imEmailCursor.getString(fi.mConvoColSummary); 2832 long last_activity = imEmailCursor.getLong(fi.mConvoColLastActivity); 2833 boolean read = (imEmailCursor.getInt(fi.mConvoColRead) == 1) ? 2834 true : false; 2835 2836 if(last_activity != convoElement.getLastActivity()) { 2837 convoChanged = true; 2838 convoElement.setLastActivity(last_activity); 2839 } 2840 2841 if(read != convoElement.getReadBool()) { 2842 convoChanged = true; 2843 convoElement.setRead(read, false); 2844 } 2845 2846 if(name != null && !name.equals(convoElement.getName())) { 2847 convoChanged = true; 2848 convoElement.setName(name); 2849 } 2850 2851 if(summary != null && !summary.equals(convoElement.getFullSummary())) { 2852 convoChanged = true; 2853 convoElement.setSummary(summary); 2854 } 2855 /* If the query returned one row for each contact, skip all the dublicates */ 2856 do { 2857 nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId); 2858 if(V) Log.i(TAG, " threadId = " + id + " newThreadId = " + 2859 nextThreadId); 2860 } while ((nextThreadId == id) && 2861 (isValid = imEmailCursor.moveToNext() == true)); 2862 2863 if(convoChanged) { 2864 listChangeDetected = true; 2865 convoElement.incrementVersionCounter(); 2866 } 2867 newList.put(id, convoElement); 2868 } 2869 // If we still have items on the old list, something was deleted 2870 if(getImEmailConvoList().size() != 0) { 2871 listChangeDetected = true; 2872 } 2873 setImEmailConvoList(newList); 2874 } 2875 } 2876 } finally { 2877 if(imEmailCursor != null) { 2878 imEmailCursor.close(); 2879 } 2880 } 2881 2882 if(listChangeDetected) { 2883 mMasInstance.updateImEmailConvoListVersionCounter(); 2884 } 2885 return listChangeDetected; 2886 } 2887 2888 /** 2889 * Update the convoVersionCounter within the element passed as parameter. 2890 * This function has the side effect to update the ConvoListVersionCounter if needed. 2891 * This function ignores changes to contacts as this shall not change the convoVersionCounter, 2892 * only the convoListVersion counter, which will be updated upon request. 2893 * @param ele Element to update shall not be null. 2894 */ updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele)2895 private void updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele) { 2896 long id = ele.getCpConvoId(); 2897 BluetoothMapConvoListingElement convoElement = getSmsMmsConvoList().get(id); 2898 boolean listChangeDetected = false; 2899 boolean convoChanged = false; 2900 if(convoElement == null) { 2901 // New conversation added 2902 convoElement = new BluetoothMapConvoListingElement(); 2903 getSmsMmsConvoList().put(id, convoElement); 2904 convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id); 2905 listChangeDetected = true; 2906 convoElement.setVersionCounter(0); 2907 } 2908 long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE); 2909 boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ? 2910 true : false; 2911 2912 if(last_activity != convoElement.getLastActivity()) { 2913 convoChanged = true; 2914 convoElement.setLastActivity(last_activity); 2915 } 2916 2917 if(read != convoElement.getReadBool()) { 2918 convoChanged = true; 2919 convoElement.setRead(read, false); 2920 } 2921 2922 if(convoChanged) { 2923 listChangeDetected = true; 2924 convoElement.incrementVersionCounter(); 2925 } 2926 if(listChangeDetected) { 2927 mMasInstance.updateSmsMmsConvoListVersionCounter(); 2928 } 2929 ele.setVersionCounter(convoElement.getVersionCounter()); 2930 } 2931 2932 /** 2933 * Update the convoVersionCounter within the element passed as parameter. 2934 * This function has the side effect to update the ConvoListVersionCounter if needed. 2935 * This function ignores changes to contacts as this shall not change the convoVersionCounter, 2936 * only the convoListVersion counter, which will be updated upon request. 2937 * @param ele Element to update shall not be null. 2938 */ updateImEmailConvoVersion(Cursor cursor, FilterInfo fi, BluetoothMapConvoListingElement ele)2939 private void updateImEmailConvoVersion(Cursor cursor, FilterInfo fi, 2940 BluetoothMapConvoListingElement ele) { 2941 long id = ele.getCpConvoId(); 2942 BluetoothMapConvoListingElement convoElement = getImEmailConvoList().get(id); 2943 boolean listChangeDetected = false; 2944 boolean convoChanged = false; 2945 if(convoElement == null) { 2946 // New conversation added 2947 if(V) Log.d(TAG, "Added new conversation with ID = " + id); 2948 convoElement = new BluetoothMapConvoListingElement(); 2949 convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id); 2950 getImEmailConvoList().put(id, convoElement); 2951 listChangeDetected = true; 2952 convoElement.setVersionCounter(0); 2953 } 2954 String name = cursor.getString(fi.mConvoColName); 2955 long last_activity = cursor.getLong(fi.mConvoColLastActivity); 2956 boolean read = (cursor.getInt(fi.mConvoColRead) == 1) ? 2957 true : false; 2958 2959 if(last_activity != convoElement.getLastActivity()) { 2960 convoChanged = true; 2961 convoElement.setLastActivity(last_activity); 2962 } 2963 2964 if(read != convoElement.getReadBool()) { 2965 convoChanged = true; 2966 convoElement.setRead(read, false); 2967 } 2968 2969 if(name != null && !name.equals(convoElement.getName())) { 2970 convoChanged = true; 2971 convoElement.setName(name); 2972 } 2973 2974 if(convoChanged) { 2975 listChangeDetected = true; 2976 if(V) Log.d(TAG, "conversation with ID = " + id + " changed"); 2977 convoElement.incrementVersionCounter(); 2978 } 2979 if(listChangeDetected) { 2980 mMasInstance.updateImEmailConvoListVersionCounter(); 2981 } 2982 ele.setVersionCounter(convoElement.getVersionCounter()); 2983 } 2984 2985 /** 2986 * @param ele 2987 * @param smsMmsCursor 2988 * @param ap 2989 * @param contacts 2990 */ populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele, Cursor smsMmsCursor, BluetoothMapAppParams ap, SmsMmsContacts contacts)2991 private void populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele, 2992 Cursor smsMmsCursor, BluetoothMapAppParams ap, 2993 SmsMmsContacts contacts) { 2994 smsMmsCursor.moveToPosition(ele.getCursorIndex()); 2995 // TODO: If we ever get beyond 31 bit, change to long 2996 int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value 2997 2998 // TODO: How to determine whether the convo-IDs can be used across message 2999 // types? 3000 ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, 3001 smsMmsCursor.getLong(MMS_SMS_THREAD_COL_ID)); 3002 3003 boolean read = (smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ? 3004 true : false; 3005 if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) { 3006 ele.setRead(read, true); 3007 } else { 3008 ele.setRead(read, false); 3009 } 3010 3011 if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) { 3012 long timeStamp = smsMmsCursor.getLong(MMS_SMS_THREAD_COL_DATE); 3013 ele.setLastActivity(timeStamp); 3014 } else { 3015 // We need to delete the time stamp, if it was added for multi msg-type 3016 ele.setLastActivity(-1); 3017 } 3018 3019 if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) { 3020 updateSmsMmsConvoVersion(smsMmsCursor, ele); 3021 } 3022 3023 if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) { 3024 ele.setName(""); // We never have a thread name for SMS/MMS 3025 } 3026 3027 if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) { 3028 String summary = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET); 3029 String cs = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET_CS); 3030 if(summary != null && cs != null && !cs.equals("UTF-8")) { 3031 try { 3032 // TODO: Not sure this is how to convert to UTF-8 3033 summary = new String(summary.getBytes(cs),"UTF-8"); 3034 } catch (UnsupportedEncodingException e){/*Cannot happen*/} 3035 } 3036 ele.setSummary(summary); 3037 } 3038 3039 if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) { 3040 if(ap.getFilterRecipient() == null) { 3041 // Add contacts only if not already added 3042 String idsStr = 3043 smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); 3044 addSmsMmsContacts(ele, contacts, idsStr, null, ap); 3045 } 3046 } 3047 } 3048 3049 /** 3050 * @param ele 3051 * @param tmpCursor 3052 * @param fi 3053 */ populateImEmailConvoElement( BluetoothMapConvoListingElement ele, Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi)3054 private void populateImEmailConvoElement( BluetoothMapConvoListingElement ele, 3055 Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi) { 3056 tmpCursor.moveToPosition(ele.getCursorIndex()); 3057 // TODO: If we ever get beyond 31 bit, change to long 3058 int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value 3059 long threadId = tmpCursor.getLong(fi.mConvoColConvoId); 3060 3061 // Mandatory field 3062 ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, threadId); 3063 3064 if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) { 3065 ele.setName(tmpCursor.getString(fi.mConvoColName)); 3066 } 3067 3068 boolean reportRead = false; 3069 if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) { 3070 reportRead = true; 3071 } 3072 ele.setRead(((1==tmpCursor.getInt(fi.mConvoColRead))?true:false), reportRead); 3073 3074 long timestamp = tmpCursor.getLong(fi.mConvoColLastActivity); 3075 if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) { 3076 ele.setLastActivity(timestamp); 3077 } else { 3078 // We need to delete the time stamp, if it was added for multi msg-type 3079 ele.setLastActivity(-1); 3080 } 3081 3082 3083 if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) { 3084 updateImEmailConvoVersion(tmpCursor, fi, ele); 3085 } 3086 if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) { 3087 ele.setSummary(tmpCursor.getString(fi.mConvoColSummary)); 3088 } 3089 // TODO: For optimization, we could avoid joining the contact and convo tables 3090 // if we have no filter nor this bit is set. 3091 if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) { 3092 do { 3093 BluetoothMapConvoContactElement c = new BluetoothMapConvoContactElement(); 3094 if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) { 3095 c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid),0)); 3096 } 3097 if((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) { 3098 c.setChatState(tmpCursor.getInt(fi.mContactColChatState)); 3099 } 3100 if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) { 3101 c.setPresenceAvailability(tmpCursor.getInt(fi.mContactColPresenceState)); 3102 } 3103 if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) { 3104 c.setPresenceStatus(tmpCursor.getString(fi.mContactColPresenceText)); 3105 } 3106 if((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) { 3107 c.setPriority(tmpCursor.getInt(fi.mContactColPriority)); 3108 } 3109 if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) { 3110 c.setDisplayName(tmpCursor.getString(fi.mContactColNickname)); 3111 } 3112 if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) { 3113 c.setContactId(tmpCursor.getString(fi.mContactColContactUci)); 3114 } 3115 if((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) { 3116 c.setLastActivity(tmpCursor.getLong(fi.mContactColLastActive)); 3117 } 3118 if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) { 3119 c.setName(tmpCursor.getString(fi.mContactColName)); 3120 } 3121 ele.addContact(c); 3122 } while (tmpCursor.moveToNext() == true 3123 && tmpCursor.getLong(fi.mConvoColConvoId) == threadId); 3124 } 3125 } 3126 3127 /** 3128 * Extract the ConvoList parameters from appParams and build the matching URI with 3129 * query parameters. 3130 * @param ap the appParams from the request 3131 * @param contentUri the URI to append parameters to 3132 * @return the new URI with the appended parameters (if any) 3133 */ appendConvoListQueryParameters(BluetoothMapAppParams ap, Uri contentUri)3134 private Uri appendConvoListQueryParameters(BluetoothMapAppParams ap, 3135 Uri contentUri) { 3136 Builder newUri = contentUri.buildUpon(); 3137 String str = ap.getFilterRecipient(); 3138 if(str != null) { 3139 str = str.trim(); 3140 str = str.replace("*", "%"); 3141 newUri.appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, str); 3142 } 3143 long time = ap.getFilterLastActivityBegin(); 3144 if(time > 0) { 3145 newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN, 3146 Long.toString(time)); 3147 } 3148 time = ap.getFilterLastActivityEnd(); 3149 if(time > 0) { 3150 newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_END, 3151 Long.toString(time)); 3152 } 3153 int readStatus = ap.getFilterReadStatus(); 3154 if(readStatus > 0) { 3155 if(readStatus == 1) { 3156 // Conversations with Unread messages only 3157 newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS, 3158 "false"); 3159 }else if(readStatus == 2) { 3160 // Conversations with all read messages only 3161 newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS, 3162 "true"); 3163 } 3164 // if both are set it will be the same as requesting an empty list, but 3165 // as it makes no sense with such a structure in a bit mask, we treat 3166 // requesting both the same as no filtering. 3167 } 3168 long convoId = -1; 3169 if(ap.getFilterConvoId() != null) { 3170 convoId = ap.getFilterConvoId().getLeastSignificantBits(); 3171 } 3172 if(convoId > 0) { 3173 newUri.appendQueryParameter(BluetoothMapContract.FILTER_THREAD_ID, 3174 Long.toString(convoId)); 3175 } 3176 return newUri.build(); 3177 } 3178 3179 /** 3180 * Procedure if we have a filter: 3181 * - loop through all ids to examine if there is a match (this will build the cache) 3182 * - If there is a match loop again to add all contacts. 3183 * 3184 * Procedure if we don't have a filter 3185 * - Add all contacts 3186 * 3187 * @param convoElement 3188 * @param contacts 3189 * @param idsStr 3190 * @param recipientFilter 3191 * @return 3192 */ addSmsMmsContacts( BluetoothMapConvoListingElement convoElement, SmsMmsContacts contacts, String idsStr, String recipientFilter, BluetoothMapAppParams ap)3193 private boolean addSmsMmsContacts( BluetoothMapConvoListingElement convoElement, 3194 SmsMmsContacts contacts, String idsStr, String recipientFilter, 3195 BluetoothMapAppParams ap) { 3196 BluetoothMapConvoContactElement contactElement; 3197 int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value 3198 boolean foundContact = false; 3199 String[] ids = idsStr.split(" "); 3200 long[] longIds = new long[ids.length]; 3201 if(recipientFilter != null) { 3202 recipientFilter = recipientFilter.trim(); 3203 } 3204 3205 for (int i = 0; i < ids.length; i++) { 3206 long longId; 3207 try { 3208 longId = Long.parseLong(ids[i]); 3209 longIds[i] = longId; 3210 if(recipientFilter == null) { 3211 // If there is not filter, all we need to do is to parse the ids 3212 foundContact = true; 3213 continue; 3214 } 3215 String addr = contacts.getPhoneNumber(mResolver, longId); 3216 if(addr == null) { 3217 // This can only happen if all messages from a contact is deleted while 3218 // performing the query. 3219 continue; 3220 } 3221 MapContact contact = 3222 contacts.getContactNameFromPhone(addr, mResolver, recipientFilter); 3223 if(D) { 3224 Log.d(TAG, " id " + longId + ": " + addr); 3225 if(contact != null) { 3226 Log.d(TAG," contact name: " + contact.getName() + " X-BT-UID: " 3227 + contact.getXBtUid()); 3228 } 3229 } 3230 if(contact == null) { 3231 continue; 3232 } 3233 foundContact = true; 3234 } catch (NumberFormatException ex) { 3235 // skip this id 3236 continue; 3237 } 3238 } 3239 3240 if(foundContact == true) { 3241 foundContact = false; 3242 for (long id : longIds) { 3243 String addr = contacts.getPhoneNumber(mResolver, id); 3244 if(addr == null) { 3245 // This can only happen if all messages from a contact is deleted while 3246 // performing the query. 3247 continue; 3248 } 3249 foundContact = true; 3250 MapContact contact = contacts.getContactNameFromPhone(addr, mResolver); 3251 3252 if(contact == null) { 3253 // We do not have a contact, we need to manually add one 3254 contactElement = new BluetoothMapConvoContactElement(); 3255 if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) { 3256 contactElement.setName(addr); // Use the phone number as name 3257 } 3258 if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) { 3259 contactElement.setContactId(addr); 3260 } 3261 } else { 3262 contactElement = BluetoothMapConvoContactElement 3263 .createFromMapContact(contact, addr); 3264 // Remove the parameters not to be reported 3265 if((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) { 3266 contactElement.setContactId(null); 3267 } 3268 if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) { 3269 contactElement.setBtUid(null); 3270 } 3271 if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) { 3272 contactElement.setDisplayName(null); 3273 } 3274 } 3275 convoElement.addContact(contactElement); 3276 } 3277 } 3278 return foundContact; 3279 } 3280 3281 /** 3282 * Get the folder name of an SMS message or MMS message. 3283 * @param c the cursor pointing at the message 3284 * @return the folder name. 3285 */ getFolderName(int type, int threadId)3286 private String getFolderName(int type, int threadId) { 3287 3288 if(threadId == -1) 3289 return BluetoothMapContract.FOLDER_NAME_DELETED; 3290 3291 switch(type) { 3292 case 1: 3293 return BluetoothMapContract.FOLDER_NAME_INBOX; 3294 case 2: 3295 return BluetoothMapContract.FOLDER_NAME_SENT; 3296 case 3: 3297 return BluetoothMapContract.FOLDER_NAME_DRAFT; 3298 case 4: // Just name outbox, failed and queued "outbox" 3299 case 5: 3300 case 6: 3301 return BluetoothMapContract.FOLDER_NAME_OUTBOX; 3302 } 3303 return ""; 3304 } 3305 getMessage(String handle, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement, String version)3306 public byte[] getMessage(String handle, BluetoothMapAppParams appParams, 3307 BluetoothMapFolderElement folderElement, String version) 3308 throws UnsupportedEncodingException{ 3309 TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle); 3310 mMessageVersion = version; 3311 long id = BluetoothMapUtils.getCpHandle(handle); 3312 if(appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) { 3313 throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as" + 3314 " we always return the full message."); 3315 } 3316 switch(type) { 3317 case SMS_GSM: 3318 case SMS_CDMA: 3319 return getSmsMessage(id, appParams.getCharset()); 3320 case MMS: 3321 return getMmsMessage(id, appParams); 3322 case EMAIL: 3323 return getEmailMessage(id, appParams, folderElement); 3324 case IM: 3325 return getIMMessage(id, appParams, folderElement); 3326 } 3327 throw new IllegalArgumentException("Invalid message handle."); 3328 } 3329 setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming)3330 private String setVCardFromPhoneNumber(BluetoothMapbMessage message, 3331 String phone, boolean incoming) { 3332 String contactId = null, contactName = null; 3333 String[] phoneNumbers = new String[1]; 3334 //Handle possible exception for empty phone address 3335 if (TextUtils.isEmpty(phone)) { 3336 return contactName; 3337 } 3338 // 3339 // Use only actual phone number, because the MCE cannot know which 3340 // number the message is from. 3341 // 3342 phoneNumbers[0] = phone; 3343 String[] emailAddresses = null; 3344 Cursor p; 3345 3346 Uri uri = Uri 3347 .withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, 3348 Uri.encode(phone)); 3349 3350 String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME}; 3351 String selection = Contacts.IN_VISIBLE_GROUP + "=1"; 3352 String orderBy = Contacts._ID + " ASC"; 3353 3354 // Get the contact _ID and name 3355 p = mResolver.query(uri, projection, selection, null, orderBy); 3356 try { 3357 if (p != null && p.moveToFirst()) { 3358 contactId = p.getString(p.getColumnIndex(Contacts._ID)); 3359 contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME)); 3360 } 3361 } finally { 3362 close(p); 3363 } 3364 // Bail out if we are unable to find a contact, based on the phone number 3365 if (contactId != null) { 3366 Cursor q = null; 3367 // Fetch the contact e-mail addresses 3368 try { 3369 q = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, 3370 ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", 3371 new String[]{contactId}, 3372 null); 3373 if (q != null && q.moveToFirst()) { 3374 int i = 0; 3375 emailAddresses = new String[q.getCount()]; 3376 do { 3377 String emailAddress = q.getString(q.getColumnIndex( 3378 ContactsContract.CommonDataKinds.Email.ADDRESS)); 3379 emailAddresses[i++] = emailAddress; 3380 } while (q != null && q.moveToNext()); 3381 } 3382 } finally { 3383 close(q); 3384 } 3385 } 3386 3387 if (incoming == true) { 3388 if(V) Log.d(TAG, "Adding originator for phone:" + phone); 3389 // Use version 3.0 as we only have a formatted name 3390 message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses,null,null); 3391 } else { 3392 if(V) Log.d(TAG, "Adding recipient for phone:" + phone); 3393 // Use version 3.0 as we only have a formatted name 3394 message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses,null,null); 3395 } 3396 return contactName; 3397 } 3398 3399 public static final int MAP_MESSAGE_CHARSET_NATIVE = 0; 3400 public static final int MAP_MESSAGE_CHARSET_UTF8 = 1; 3401 getSmsMessage(long id, int charset)3402 public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException{ 3403 int type, threadId; 3404 long time = -1; 3405 String msgBody; 3406 BluetoothMapbMessageSms message = new BluetoothMapbMessageSms(); 3407 TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); 3408 3409 Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null); 3410 if (c == null || !c.moveToFirst()) { 3411 throw new IllegalArgumentException("SMS handle not found"); 3412 } 3413 3414 try{ 3415 if(c != null && c.moveToFirst()) 3416 { 3417 if(V) Log.v(TAG,"c.count: " + c.getCount()); 3418 3419 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) { 3420 message.setType(TYPE.SMS_GSM); 3421 } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 3422 message.setType(TYPE.SMS_CDMA); 3423 } 3424 message.setVersionString(mMessageVersion); 3425 String read = c.getString(c.getColumnIndex(Sms.READ)); 3426 if (read.equalsIgnoreCase("1")) 3427 message.setStatus(true); 3428 else 3429 message.setStatus(false); 3430 3431 type = c.getInt(c.getColumnIndex(Sms.TYPE)); 3432 threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); 3433 message.setFolder(getFolderName(type, threadId)); 3434 3435 msgBody = c.getString(c.getColumnIndex(Sms.BODY)); 3436 3437 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); 3438 if ((phone == null) && type == Sms.MESSAGE_TYPE_DRAFT) { 3439 //Fetch address for Drafts folder from "canonical_address" table 3440 phone = getCanonicalAddressSms(mResolver, threadId); 3441 } 3442 time = c.getLong(c.getColumnIndex(Sms.DATE)); 3443 if(type == 1) // Inbox message needs to set the vCard as originator 3444 setVCardFromPhoneNumber(message, phone, true); 3445 else // Other messages sets the vCard as the recipient 3446 setVCardFromPhoneNumber(message, phone, false); 3447 3448 if(charset == MAP_MESSAGE_CHARSET_NATIVE) { 3449 if(type == 1) //Inbox 3450 message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody, 3451 phone, time)); 3452 else 3453 message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone)); 3454 } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ { 3455 message.setSmsBody(msgBody); 3456 } 3457 return message.encode(); 3458 } 3459 } finally { 3460 if (c != null) c.close(); 3461 } 3462 3463 return message.encode(); 3464 } 3465 extractMmsAddresses(long id, BluetoothMapbMessageMime message)3466 private void extractMmsAddresses(long id, BluetoothMapbMessageMime message) { 3467 final String[] projection = null; 3468 String selection = new String(Mms.Addr.MSG_ID + "=" + id); 3469 String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr"); 3470 Uri uriAddress = Uri.parse(uriStr); 3471 String contactName = null; 3472 3473 Cursor c = mResolver.query( uriAddress, projection, selection, null, null); 3474 try { 3475 if (c.moveToFirst()) { 3476 do { 3477 String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS)); 3478 if(address.equals(INSERT_ADDRES_TOKEN)) 3479 continue; 3480 Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE)); 3481 switch(type) { 3482 case MMS_FROM: 3483 contactName = setVCardFromPhoneNumber(message, address, true); 3484 message.addFrom(contactName, address); 3485 break; 3486 case MMS_TO: 3487 contactName = setVCardFromPhoneNumber(message, address, false); 3488 message.addTo(contactName, address); 3489 break; 3490 case MMS_CC: 3491 contactName = setVCardFromPhoneNumber(message, address, false); 3492 message.addCc(contactName, address); 3493 break; 3494 case MMS_BCC: 3495 contactName = setVCardFromPhoneNumber(message, address, false); 3496 message.addBcc(contactName, address); 3497 break; 3498 default: 3499 break; 3500 } 3501 } while(c.moveToNext()); 3502 } 3503 } finally { 3504 if (c != null) c.close(); 3505 } 3506 } 3507 3508 3509 /** 3510 * Read out a mime data part and return the data in a byte array. 3511 * @param contentPartUri TODO 3512 * @param partid the content provider id of the Mime Part. 3513 * @return 3514 */ readRawDataPart(Uri contentPartUri, long partid)3515 private byte[] readRawDataPart(Uri contentPartUri, long partid) { 3516 String uriStr = new String(contentPartUri+"/"+ partid); 3517 Uri uriAddress = Uri.parse(uriStr); 3518 InputStream is = null; 3519 ByteArrayOutputStream os = new ByteArrayOutputStream(); 3520 int bufferSize = 8192; 3521 byte[] buffer = new byte[bufferSize]; 3522 byte[] retVal = null; 3523 3524 try { 3525 is = mResolver.openInputStream(uriAddress); 3526 int len = 0; 3527 while ((len = is.read(buffer)) != -1) { 3528 os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize 3529 } 3530 retVal = os.toByteArray(); 3531 } catch (IOException e) { 3532 // do nothing for now 3533 Log.w(TAG,"Error reading part data",e); 3534 } finally { 3535 close(os); 3536 close(is); 3537 } 3538 return retVal; 3539 } 3540 3541 /** 3542 * Read out the mms parts and update the bMessage object provided i {@linkplain message} 3543 * @param id the content provider ID of the message 3544 * @param message the bMessage object to add the information to 3545 */ extractMmsParts(long id, BluetoothMapbMessageMime message)3546 private void extractMmsParts(long id, BluetoothMapbMessageMime message) 3547 { 3548 /* Handling of filtering out non-text parts for exclude 3549 * attachments is handled within the bMessage object. */ 3550 final String[] projection = null; 3551 String selection = new String(Mms.Part.MSG_ID + "=" + id); 3552 String uriStr = new String(Mms.CONTENT_URI + "/"+ id + "/part"); 3553 Uri uriAddress = Uri.parse(uriStr); 3554 BluetoothMapbMessageMime.MimePart part; 3555 Cursor c = mResolver.query(uriAddress, projection, selection, null, null); 3556 try { 3557 if (c.moveToFirst()) { 3558 do { 3559 Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID)); 3560 String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE)); 3561 String name = c.getString(c.getColumnIndex(Mms.Part.NAME)); 3562 String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET)); 3563 String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME)); 3564 String text = c.getString(c.getColumnIndex(Mms.Part.TEXT)); 3565 Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA)); 3566 String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID)); 3567 String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION)); 3568 String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION)); 3569 3570 if(V) Log.d(TAG, " _id : " + partId + 3571 "\n ct : " + contentType + 3572 "\n partname : " + name + 3573 "\n charset : " + charset + 3574 "\n filename : " + filename + 3575 "\n text : " + text + 3576 "\n fd : " + fd + 3577 "\n cid : " + cid + 3578 "\n cl : " + cl + 3579 "\n cdisp : " + cdisp); 3580 3581 part = message.addMimePart(); 3582 part.mContentType = contentType; 3583 part.mPartName = name; 3584 part.mContentId = cid; 3585 part.mContentLocation = cl; 3586 part.mContentDisposition = cdisp; 3587 3588 try { 3589 if(text != null) { 3590 part.mData = text.getBytes("UTF-8"); 3591 part.mCharsetName = "utf-8"; 3592 } else { 3593 part.mData = 3594 readRawDataPart(Uri.parse(Mms.CONTENT_URI+"/part"), partId); 3595 if(charset != null) { 3596 part.mCharsetName = 3597 CharacterSets.getMimeName(Integer.parseInt(charset)); 3598 } 3599 } 3600 } catch (NumberFormatException e) { 3601 Log.d(TAG,"extractMmsParts",e); 3602 part.mData = null; 3603 part.mCharsetName = null; 3604 } catch (UnsupportedEncodingException e) { 3605 Log.d(TAG,"extractMmsParts",e); 3606 part.mData = null; 3607 part.mCharsetName = null; 3608 } finally { 3609 } 3610 part.mFileName = filename; 3611 } while(c.moveToNext()); 3612 message.updateCharset(); 3613 } 3614 3615 } finally { 3616 if(c != null) c.close(); 3617 } 3618 } 3619 /** 3620 * Read out the mms parts and update the bMessage object provided i {@linkplain message} 3621 * @param id the content provider ID of the message 3622 * @param message the bMessage object to add the information to 3623 */ extractIMParts(long id, BluetoothMapbMessageMime message)3624 private void extractIMParts(long id, BluetoothMapbMessageMime message) 3625 { 3626 /* Handling of filtering out non-text parts for exclude 3627 * attachments is handled within the bMessage object. */ 3628 final String[] projection = null; 3629 String selection = new String(BluetoothMapContract.MessageColumns._ID + "=" + id); 3630 String uriStr = new String(mBaseUri 3631 + BluetoothMapContract.TABLE_MESSAGE + "/"+ id + "/part"); 3632 Uri uriAddress = Uri.parse(uriStr); 3633 BluetoothMapbMessageMime.MimePart part; 3634 Cursor c = mResolver.query(uriAddress, projection, selection, null, null); 3635 try{ 3636 if (c.moveToFirst()) { 3637 do { 3638 Long partId = c.getLong( 3639 c.getColumnIndex(BluetoothMapContract.MessagePartColumns._ID)); 3640 String charset = c.getString( 3641 c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CHARSET)); 3642 String filename = c.getString( 3643 c.getColumnIndex(BluetoothMapContract.MessagePartColumns.FILENAME)); 3644 String text = c.getString( 3645 c.getColumnIndex(BluetoothMapContract.MessagePartColumns.TEXT)); 3646 String body = c.getString( 3647 c.getColumnIndex(BluetoothMapContract.MessagePartColumns.RAW_DATA)); 3648 String cid = c.getString( 3649 c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CONTENT_ID)); 3650 3651 if(V) Log.d(TAG, " _id : " + partId + 3652 "\n charset : " + charset + 3653 "\n filename : " + filename + 3654 "\n text : " + text + 3655 "\n cid : " + cid); 3656 3657 part = message.addMimePart(); 3658 part.mContentId = cid; 3659 try { 3660 if(text.equalsIgnoreCase("yes")) { 3661 part.mData = body.getBytes("UTF-8"); 3662 part.mCharsetName = "utf-8"; 3663 } else { 3664 part.mData = readRawDataPart(Uri.parse(mBaseUri 3665 + BluetoothMapContract.TABLE_MESSAGE_PART) , partId); 3666 if(charset != null) 3667 part.mCharsetName = CharacterSets.getMimeName( 3668 Integer.parseInt(charset)); 3669 } 3670 } catch (NumberFormatException e) { 3671 Log.d(TAG,"extractIMParts",e); 3672 part.mData = null; 3673 part.mCharsetName = null; 3674 } catch (UnsupportedEncodingException e) { 3675 Log.d(TAG,"extractIMParts",e); 3676 part.mData = null; 3677 part.mCharsetName = null; 3678 } finally { 3679 } 3680 part.mFileName = filename; 3681 } while(c.moveToNext()); 3682 } 3683 } finally { 3684 if(c != null) c.close(); 3685 } 3686 3687 message.updateCharset(); 3688 } 3689 3690 /** 3691 * 3692 * @param id the content provider id for the message to fetch. 3693 * @param appParams The application parameter object received from the client. 3694 * @return a byte[] containing the utf-8 encoded bMessage to send to the client. 3695 * @throws UnsupportedEncodingException if UTF-8 is not supported, 3696 * which is guaranteed to be supported on an android device 3697 */ getMmsMessage(long id,BluetoothMapAppParams appParams)3698 public byte[] getMmsMessage(long id,BluetoothMapAppParams appParams) 3699 throws UnsupportedEncodingException { 3700 int msgBox, threadId; 3701 if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) 3702 throw new IllegalArgumentException("MMS charset native not allowed for MMS" 3703 +" - must be utf-8"); 3704 3705 BluetoothMapbMessageMime message = new BluetoothMapbMessageMime(); 3706 Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null); 3707 try { 3708 if(c != null && c.moveToFirst()) 3709 { 3710 message.setType(TYPE.MMS); 3711 message.setVersionString(mMessageVersion); 3712 3713 // The MMS info: 3714 String read = c.getString(c.getColumnIndex(Mms.READ)); 3715 if (read.equalsIgnoreCase("1")) 3716 message.setStatus(true); 3717 else 3718 message.setStatus(false); 3719 3720 msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); 3721 threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); 3722 message.setFolder(getFolderName(msgBox, threadId)); 3723 message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT))); 3724 message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID))); 3725 message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE))); 3726 message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L); 3727 message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true); 3728 message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true); 3729 // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used 3730 // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is 3731 3732 // The parts 3733 extractMmsParts(id, message); 3734 3735 // The addresses 3736 extractMmsAddresses(id, message); 3737 3738 3739 return message.encode(); 3740 } 3741 } finally { 3742 if (c != null) c.close(); 3743 } 3744 3745 return message.encode(); 3746 } 3747 3748 /** 3749 * 3750 * @param id the content provider id for the message to fetch. 3751 * @param appParams The application parameter object received from the client. 3752 * @return a byte[] containing the utf-8 encoded bMessage to send to the client. 3753 * @throws UnsupportedEncodingException if UTF-8 is not supported, 3754 * which is guaranteed to be supported on an android device 3755 */ getEmailMessage(long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement currentFolder)3756 public byte[] getEmailMessage(long id, BluetoothMapAppParams appParams, 3757 BluetoothMapFolderElement currentFolder) throws UnsupportedEncodingException { 3758 // Log print out of application parameters set 3759 if(D && appParams != null) { 3760 Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() + 3761 ", Charset = " + appParams.getCharset() + 3762 ", FractionRequest = " + appParams.getFractionRequest()); 3763 } 3764 3765 // Throw exception if requester NATIVE charset for Email 3766 // Exception is caught by MapObexServer sendGetMessageResp 3767 if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) 3768 throw new IllegalArgumentException("EMAIL charset not UTF-8"); 3769 3770 BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail(); 3771 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 3772 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " 3773 + id, null, null); 3774 try { 3775 if(c != null && c.moveToFirst()) 3776 { 3777 BluetoothMapFolderElement folderElement; 3778 FileInputStream is = null; 3779 ParcelFileDescriptor fd = null; 3780 try { 3781 // Handle fraction requests 3782 int fractionRequest = appParams.getFractionRequest(); 3783 if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 3784 // Fraction requested 3785 if(V) { 3786 String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT"; 3787 Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr 3788 + " - send compete message" ); 3789 } 3790 // Check if message is complete and if not - request message from server 3791 if (c.getString(c.getColumnIndex( 3792 BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase( 3793 BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false) { 3794 // TODO: request message from server 3795 Log.w(TAG, "getEmailMessage - receptionState not COMPLETE - Not Implemented!" ); 3796 } 3797 } 3798 // Set read status: 3799 String read = c.getString( 3800 c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ)); 3801 if (read != null && read.equalsIgnoreCase("1")) 3802 message.setStatus(true); 3803 else 3804 message.setStatus(false); 3805 3806 // Set message type: 3807 message.setType(TYPE.EMAIL); 3808 message.setVersionString(mMessageVersion); 3809 // Set folder: 3810 long folderId = c.getLong( 3811 c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID)); 3812 folderElement = currentFolder.getFolderById(folderId); 3813 message.setCompleteFolder(folderElement.getFullPath()); 3814 3815 // Set recipient: 3816 String nameEmail = c.getString( 3817 c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST)); 3818 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail); 3819 if (tokens.length != 0) { 3820 if(D) Log.d(TAG, "Recipient count= " + tokens.length); 3821 int i = 0; 3822 while (i < tokens.length) { 3823 if(V) Log.d(TAG, "Recipient = " + tokens[i].toString()); 3824 String[] emails = new String[1]; 3825 emails[0] = tokens[i].getAddress(); 3826 String name = tokens[i].getName(); 3827 message.addRecipient(name, name, null, emails, null, null); 3828 i++; 3829 } 3830 } 3831 3832 // Set originator: 3833 nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST)); 3834 tokens = Rfc822Tokenizer.tokenize(nameEmail); 3835 if (tokens.length != 0) { 3836 if(D) Log.d(TAG, "Originator count= " + tokens.length); 3837 int i = 0; 3838 while (i < tokens.length) { 3839 if(V) Log.d(TAG, "Originator = " + tokens[i].toString()); 3840 String[] emails = new String[1]; 3841 emails[0] = tokens[i].getAddress(); 3842 String name = tokens[i].getName(); 3843 message.addOriginator(name, name, null, emails, null, null); 3844 i++; 3845 } 3846 } 3847 } finally { 3848 if(c != null) c.close(); 3849 } 3850 // Find out if we get attachments 3851 String attStr = (appParams.getAttachment() == 0) ? 3852 "/" + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : ""; 3853 Uri uri = Uri.parse(contentUri + "/" + id + attStr); 3854 3855 // Get email message body content 3856 int count = 0; 3857 try { 3858 fd = mResolver.openFileDescriptor(uri, "r"); 3859 is = new FileInputStream(fd.getFileDescriptor()); 3860 StringBuilder email = new StringBuilder(""); 3861 byte[] buffer = new byte[1024]; 3862 while((count = is.read(buffer)) != -1) { 3863 // TODO: Handle breaks within a UTF8 character 3864 email.append(new String(buffer,0,count)); 3865 if(V) Log.d(TAG, "Email part = " 3866 + new String(buffer,0,count) 3867 + " count=" + count); 3868 } 3869 // Set email message body: 3870 message.setEmailBody(email.toString()); 3871 } catch (FileNotFoundException e) { 3872 Log.w(TAG, e); 3873 } catch (NullPointerException e) { 3874 Log.w(TAG, e); 3875 } catch (IOException e) { 3876 Log.w(TAG, e); 3877 } finally { 3878 try { 3879 if(is != null) is.close(); 3880 } catch (IOException e) {} 3881 try { 3882 if(fd != null) fd.close(); 3883 } catch (IOException e) {} 3884 } 3885 return message.encode(); 3886 } 3887 } finally { 3888 if (c != null) c.close(); 3889 } 3890 throw new IllegalArgumentException("EMAIL handle not found"); 3891 } 3892 /** 3893 * 3894 * @param id the content provider id for the message to fetch. 3895 * @param appParams The application parameter object received from the client. 3896 * @return a byte[] containing the UTF-8 encoded bMessage to send to the client. 3897 * @throws UnsupportedEncodingException if UTF-8 is not supported, 3898 * which is guaranteed to be supported on an android device 3899 */ 3900 3901 /** 3902 * 3903 * @param id the content provider id for the message to fetch. 3904 * @param appParams The application parameter object received from the client. 3905 * @return a byte[] containing the utf-8 encoded bMessage to send to the client. 3906 * @throws UnsupportedEncodingException if UTF-8 is not supported, 3907 * which is guaranteed to be supported on an android device 3908 */ getIMMessage(long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement)3909 public byte[] getIMMessage(long id, 3910 BluetoothMapAppParams appParams, 3911 BluetoothMapFolderElement folderElement) 3912 throws UnsupportedEncodingException { 3913 long threadId, folderId; 3914 3915 if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) 3916 throw new IllegalArgumentException( 3917 "IM charset native not allowed for IM - must be utf-8"); 3918 3919 BluetoothMapbMessageMime message = new BluetoothMapbMessageMime(); 3920 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); 3921 Cursor c = mResolver.query(contentUri, 3922 BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null); 3923 Cursor contacts = null; 3924 try { 3925 if(c != null && c.moveToFirst()) { 3926 message.setType(TYPE.IM); 3927 message.setVersionString(mMessageVersion); 3928 3929 // The IM message info: 3930 int read = 3931 c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ)); 3932 if (read == 1) 3933 message.setStatus(true); 3934 else 3935 message.setStatus(false); 3936 3937 threadId = 3938 c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID)); 3939 folderId = 3940 c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID)); 3941 folderElement = folderElement.getFolderById(folderId); 3942 message.setCompleteFolder(folderElement.getFullPath()); 3943 message.setSubject(c.getString( 3944 c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT))); 3945 message.setMessageId(c.getString( 3946 c.getColumnIndex(BluetoothMapContract.MessageColumns._ID))); 3947 message.setDate(c.getLong( 3948 c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE))); 3949 message.setTextOnly(c.getInt(c.getColumnIndex( 3950 BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE)) != 0 ? false : true); 3951 3952 message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true); 3953 3954 // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used 3955 // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is 3956 3957 // The parts 3958 3959 //FIXME use the parts when ready - until then use the body column for text-only 3960 // extractIMParts(id, message); 3961 //FIXME next few lines are temporary code 3962 MimePart part = message.addMimePart(); 3963 part.mData = c.getString((c.getColumnIndex( 3964 BluetoothMapContract.MessageColumns.BODY))).getBytes("UTF-8"); 3965 part.mCharsetName = "utf-8"; 3966 part.mContentId = "0"; 3967 part.mContentType = "text/plain"; 3968 message.updateCharset(); 3969 // FIXME end temp code 3970 3971 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); 3972 contacts = mResolver.query(contactsUri, 3973 BluetoothMapContract.BT_CONTACT_PROJECTION, 3974 BluetoothMapContract.ConvoContactColumns.CONVO_ID 3975 + " = " + threadId, null, null); 3976 // TODO this will not work for group-chats 3977 if(contacts != null && contacts.moveToFirst()){ 3978 String name = contacts.getString(contacts.getColumnIndex( 3979 BluetoothMapContract.ConvoContactColumns.NAME)); 3980 String btUid[] = new String[1]; 3981 btUid[0]= contacts.getString(contacts.getColumnIndex( 3982 BluetoothMapContract.ConvoContactColumns.X_BT_UID)); 3983 String nickname = contacts.getString(contacts.getColumnIndex( 3984 BluetoothMapContract.ConvoContactColumns.NICKNAME)); 3985 String btUci[] = new String[1]; 3986 String btOwnUci[] = new String[1]; 3987 btOwnUci[0] = mAccount.getUciFull(); 3988 btUci[0] = contacts.getString(contacts.getColumnIndex( 3989 BluetoothMapContract.ConvoContactColumns.UCI)); 3990 if(folderId == BluetoothMapContract.FOLDER_ID_SENT 3991 || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) { 3992 message.addRecipient(nickname,name,null, null, btUid, btUci); 3993 message.addOriginator(null, btOwnUci); 3994 3995 }else { 3996 message.addOriginator(nickname,name,null, null, btUid, btUci); 3997 message.addRecipient(null, btOwnUci); 3998 3999 } 4000 } 4001 return message.encode(); 4002 } 4003 } finally { 4004 if(c != null) c.close(); 4005 if(contacts != null) contacts.close(); 4006 } 4007 4008 throw new IllegalArgumentException("IM handle not found"); 4009 } 4010 setRemoteFeatureMask(int featureMask)4011 public void setRemoteFeatureMask(int featureMask){ 4012 this.mRemoteFeatureMask = featureMask; 4013 if(V) Log.d(TAG, "setRemoteFeatureMask"); 4014 if((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) 4015 == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) { 4016 if(V) Log.d(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11"); 4017 this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11; 4018 } 4019 } 4020 getRemoteFeatureMask()4021 public int getRemoteFeatureMask(){ 4022 return this.mRemoteFeatureMask; 4023 } 4024 getSmsMmsConvoList()4025 HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() { 4026 return mMasInstance.getSmsMmsConvoList(); 4027 } 4028 setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList)4029 void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) { 4030 mMasInstance.setSmsMmsConvoList(smsMmsConvoList); 4031 } 4032 getImEmailConvoList()4033 HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() { 4034 return mMasInstance.getImEmailConvoList(); 4035 } 4036 setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList)4037 void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) { 4038 mMasInstance.setImEmailConvoList(imEmailConvoList); 4039 } 4040 } 4041