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