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