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