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