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