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