/* * Copyright (C) 2014 Samsung System LSI * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.map; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.net.Uri.Builder; import android.os.ParcelFileDescriptor; import android.provider.BaseColumns; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.PhoneLookup; import android.provider.Telephony.CanonicalAddressesColumns; import android.provider.Telephony.Mms; import android.provider.Telephony.MmsSms; import android.provider.Telephony.Sms; import android.provider.Telephony.Threads; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.util.Rfc822Token; import android.text.util.Rfc822Tokenizer; import android.util.Log; import com.android.bluetooth.DeviceWorkArounds; import com.android.bluetooth.SignedLongLong; import com.android.bluetooth.map.BluetoothMapUtils.TYPE; import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart; import com.android.bluetooth.mapapi.BluetoothMapContract; import com.android.bluetooth.mapapi.BluetoothMapContract.ConversationColumns; import com.google.android.mms.pdu.CharacterSets; import com.google.android.mms.pdu.PduHeaders; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @TargetApi(19) public class BluetoothMapContent { private static final String TAG = "BluetoothMapContent"; private static final boolean D = BluetoothMapService.DEBUG; private static final boolean V = BluetoothMapService.VERBOSE; // Parameter Mask for selection of parameters to return in listings private static final int MASK_SUBJECT = 0x00000001; private static final int MASK_DATETIME = 0x00000002; private static final int MASK_SENDER_NAME = 0x00000004; private static final int MASK_SENDER_ADDRESSING = 0x00000008; private static final int MASK_RECIPIENT_NAME = 0x00000010; private static final int MASK_RECIPIENT_ADDRESSING = 0x00000020; private static final int MASK_TYPE = 0x00000040; private static final int MASK_SIZE = 0x00000080; private static final int MASK_RECEPTION_STATUS = 0x00000100; private static final int MASK_TEXT = 0x00000200; private static final int MASK_ATTACHMENT_SIZE = 0x00000400; private static final int MASK_PRIORITY = 0x00000800; private static final int MASK_READ = 0x00001000; private static final int MASK_SENT = 0x00002000; private static final int MASK_PROTECTED = 0x00004000; private static final int MASK_REPLYTO_ADDRESSING = 0x00008000; // TODO: Duplicate in proposed spec // private static final int MASK_RECEPTION_STATE = 0x00010000; private static final int MASK_DELIVERY_STATUS = 0x00010000; private static final int MASK_CONVERSATION_ID = 0x00020000; private static final int MASK_CONVERSATION_NAME = 0x00040000; private static final int MASK_FOLDER_TYPE = 0x00100000; // TODO: about to be removed from proposed spec // private static final int MASK_SEQUENCE_NUMBER = 0x00200000; private static final int MASK_ATTACHMENT_MIME = 0x00100000; private static final int CONVO_PARAM_MASK_CONVO_NAME = 0x00000001; private static final int CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY = 0x00000002; private static final int CONVO_PARAM_MASK_CONVO_READ_STATUS = 0x00000004; private static final int CONVO_PARAM_MASK_CONVO_VERSION_COUNTER = 0x00000008; private static final int CONVO_PARAM_MASK_CONVO_SUMMARY = 0x00000010; private static final int CONVO_PARAM_MASK_PARTTICIPANTS = 0x00000020; private static final int CONVO_PARAM_MASK_PART_UCI = 0x00000040; private static final int CONVO_PARAM_MASK_PART_DISP_NAME = 0x00000080; private static final int CONVO_PARAM_MASK_PART_CHAT_STATE = 0x00000100; private static final int CONVO_PARAM_MASK_PART_LAST_ACTIVITY = 0x00000200; private static final int CONVO_PARAM_MASK_PART_X_BT_UID = 0x00000400; private static final int CONVO_PARAM_MASK_PART_NAME = 0x00000800; private static final int CONVO_PARAM_MASK_PART_PRESENCE = 0x00001000; private static final int CONVO_PARAM_MASK_PART_PRESENCE_TEXT = 0x00002000; private static final int CONVO_PARAM_MASK_PART_PRIORITY = 0x00004000; /* Default values for omitted or 0 parameterMask application parameters */ // MAP specification states that the default value for parameter mask are // the #REQUIRED attributes in the DTD, and not all enabled public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL; public static final long CONVO_PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL; public static final long CONVO_PARAMETER_MASK_DEFAULT = CONVO_PARAM_MASK_CONVO_NAME | CONVO_PARAM_MASK_PARTTICIPANTS | CONVO_PARAM_MASK_PART_UCI | CONVO_PARAM_MASK_PART_DISP_NAME; private static final int FILTER_READ_STATUS_UNREAD_ONLY = 0x01; private static final int FILTER_READ_STATUS_READ_ONLY = 0x02; private static final int FILTER_READ_STATUS_ALL = 0x00; /* Type of MMS address. From Telephony.java it must be one of PduHeaders.BCC, */ /* PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO. These are from PduHeaders.java */ public static final int MMS_FROM = 0x89; public static final int MMS_TO = 0x97; public static final int MMS_BCC = 0x81; public static final int MMS_CC = 0x82; /* OMA-TS-MMS-ENC defined many types in X-Mms-Message-Type. Only m-send-req (128) m-retrieve-conf (132), m-notification-ind (130) are interested by user */ private static final String INTERESTED_MESSAGE_TYPE_CLAUSE = String.format("( %s = %d OR %s = %d OR %s = %d )", Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ, Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF, Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); public static final String INSERT_ADDRES_TOKEN = "insert-address-token"; private final Context mContext; private final ContentResolver mResolver; private final String mBaseUri; private final BluetoothMapAccountItem mAccount; /* The MasInstance reference is used to update persistent (over a connection) version counters*/ private final BluetoothMapMasInstance mMasInstance; private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR; private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK; private int mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10; static final String[] SMS_PROJECTION = new String[]{ BaseColumns._ID, Sms.THREAD_ID, Sms.ADDRESS, Sms.BODY, Sms.DATE, Sms.READ, Sms.TYPE, Sms.STATUS, Sms.LOCKED, Sms.ERROR_CODE }; static final String[] MMS_PROJECTION = new String[]{ BaseColumns._ID, Mms.THREAD_ID, Mms.MESSAGE_ID, Mms.MESSAGE_SIZE, Mms.SUBJECT, Mms.CONTENT_TYPE, Mms.TEXT_ONLY, Mms.DATE, Mms.DATE_SENT, Mms.READ, Mms.MESSAGE_BOX, Mms.STATUS, Mms.PRIORITY, }; static final String[] SMS_CONVO_PROJECTION = new String[]{ BaseColumns._ID, Sms.THREAD_ID, Sms.ADDRESS, Sms.DATE, Sms.READ, Sms.TYPE, Sms.STATUS, Sms.LOCKED, Sms.ERROR_CODE }; static final String[] MMS_CONVO_PROJECTION = new String[]{ BaseColumns._ID, Mms.THREAD_ID, Mms.MESSAGE_ID, Mms.MESSAGE_SIZE, Mms.SUBJECT, Mms.CONTENT_TYPE, Mms.TEXT_ONLY, Mms.DATE, Mms.DATE_SENT, Mms.READ, Mms.MESSAGE_BOX, Mms.STATUS, Mms.PRIORITY, Mms.Addr.ADDRESS }; /* CONVO LISTING projections and column indexes */ private static final String[] MMS_SMS_THREAD_PROJECTION = { Threads._ID, Threads.DATE, Threads.SNIPPET, Threads.SNIPPET_CHARSET, Threads.READ, Threads.RECIPIENT_IDS }; private static final String[] CONVO_VERSION_PROJECTION = new String[]{ /* Thread information */ ConversationColumns.THREAD_ID, ConversationColumns.THREAD_NAME, ConversationColumns.READ_STATUS, ConversationColumns.LAST_THREAD_ACTIVITY, ConversationColumns.SUMMARY, }; /* Optimize the Cursor access to avoid the need to do a getColumnIndex() */ private static final int MMS_SMS_THREAD_COL_ID; private static final int MMS_SMS_THREAD_COL_DATE; private static final int MMS_SMS_THREAD_COL_SNIPPET; private static final int MMS_SMS_THREAD_COL_SNIPPET_CS; private static final int MMS_SMS_THREAD_COL_READ; private static final int MMS_SMS_THREAD_COL_RECIPIENT_IDS; static { // TODO: This might not work, if the projection is mapped in the content provider... // Change to init at first query? (Current use in the AOSP code is hard coded values // unrelated to the projection used) List projection = Arrays.asList(MMS_SMS_THREAD_PROJECTION); MMS_SMS_THREAD_COL_ID = projection.indexOf(Threads._ID); MMS_SMS_THREAD_COL_DATE = projection.indexOf(Threads.DATE); MMS_SMS_THREAD_COL_SNIPPET = projection.indexOf(Threads.SNIPPET); MMS_SMS_THREAD_COL_SNIPPET_CS = projection.indexOf(Threads.SNIPPET_CHARSET); MMS_SMS_THREAD_COL_READ = projection.indexOf(Threads.READ); MMS_SMS_THREAD_COL_RECIPIENT_IDS = projection.indexOf(Threads.RECIPIENT_IDS); } private class FilterInfo { public static final int TYPE_SMS = 0; public static final int TYPE_MMS = 1; public static final int TYPE_EMAIL = 2; public static final int TYPE_IM = 3; // TODO: Change to ENUM, to ensure correct usage int mMsgType = TYPE_SMS; int mPhoneType = 0; String mPhoneNum = null; String mPhoneAlphaTag = null; /*column indices used to optimize queries */ public int mMessageColId = -1; public int mMessageColDate = -1; public int mMessageColBody = -1; public int mMessageColSubject = -1; public int mMessageColFolder = -1; public int mMessageColRead = -1; public int mMessageColSize = -1; public int mMessageColFromAddress = -1; public int mMessageColToAddress = -1; public int mMessageColCcAddress = -1; public int mMessageColBccAddress = -1; public int mMessageColReplyTo = -1; public int mMessageColAccountId = -1; public int mMessageColAttachment = -1; public int mMessageColAttachmentSize = -1; public int mMessageColAttachmentMime = -1; public int mMessageColPriority = -1; public int mMessageColProtected = -1; public int mMessageColReception = -1; public int mMessageColDelivery = -1; public int mMessageColThreadId = -1; public int mMessageColThreadName = -1; public int mSmsColFolder = -1; public int mSmsColRead = -1; public int mSmsColId = -1; public int mSmsColSubject = -1; public int mSmsColAddress = -1; public int mSmsColDate = -1; public int mSmsColType = -1; public int mSmsColThreadId = -1; public int mMmsColRead = -1; public int mMmsColFolder = -1; public int mMmsColAttachmentSize = -1; public int mMmsColTextOnly = -1; public int mMmsColId = -1; public int mMmsColSize = -1; public int mMmsColDate = -1; public int mMmsColSubject = -1; public int mMmsColThreadId = -1; public int mConvoColConvoId = -1; public int mConvoColLastActivity = -1; public int mConvoColName = -1; public int mConvoColRead = -1; public int mConvoColVersionCounter = -1; public int mConvoColSummary = -1; public int mContactColBtUid = -1; public int mContactColChatState = -1; public int mContactColContactUci = -1; public int mContactColNickname = -1; public int mContactColLastActive = -1; public int mContactColName = -1; public int mContactColPresenceState = -1; public int mContactColPresenceText = -1; public int mContactColPriority = -1; public void setMessageColumns(Cursor c) { mMessageColId = c.getColumnIndex(BluetoothMapContract.MessageColumns._ID); mMessageColDate = c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE); mMessageColSubject = c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT); mMessageColFolder = c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID); mMessageColRead = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ); mMessageColSize = c.getColumnIndex(BluetoothMapContract.MessageColumns.MESSAGE_SIZE); mMessageColFromAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST); mMessageColToAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST); mMessageColAttachment = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT); mMessageColAttachmentSize = c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE); mMessageColPriority = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY); mMessageColProtected = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_PROTECTED); mMessageColReception = c.getColumnIndex(BluetoothMapContract.MessageColumns.RECEPTION_STATE); mMessageColDelivery = c.getColumnIndex(BluetoothMapContract.MessageColumns.DEVILERY_STATE); mMessageColThreadId = c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID); } public void setEmailMessageColumns(Cursor c) { setMessageColumns(c); mMessageColCcAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.CC_LIST); mMessageColBccAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.BCC_LIST); mMessageColReplyTo = c.getColumnIndex(BluetoothMapContract.MessageColumns.REPLY_TO_LIST); } public void setImMessageColumns(Cursor c) { setMessageColumns(c); mMessageColThreadName = c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_NAME); mMessageColAttachmentMime = c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES); //TODO this is temporary as text should come from parts table instead mMessageColBody = c.getColumnIndex(BluetoothMapContract.MessageColumns.BODY); } public void setEmailImConvoColumns(Cursor c) { mConvoColConvoId = c.getColumnIndex(BluetoothMapContract.ConversationColumns.THREAD_ID); mConvoColLastActivity = c.getColumnIndex(BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY); mConvoColName = c.getColumnIndex(BluetoothMapContract.ConversationColumns.THREAD_NAME); mConvoColRead = c.getColumnIndex(BluetoothMapContract.ConversationColumns.READ_STATUS); mConvoColVersionCounter = c.getColumnIndex(BluetoothMapContract.ConversationColumns.VERSION_COUNTER); mConvoColSummary = c.getColumnIndex(BluetoothMapContract.ConversationColumns.SUMMARY); setEmailImConvoContactColumns(c); } public void setEmailImConvoContactColumns(Cursor c) { mContactColBtUid = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.X_BT_UID); mContactColChatState = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.CHAT_STATE); mContactColContactUci = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.UCI); mContactColNickname = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NICKNAME); mContactColLastActive = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE); mContactColName = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME); mContactColPresenceState = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE); mContactColPresenceText = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.STATUS_TEXT); mContactColPriority = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.PRIORITY); } public void setSmsColumns(Cursor c) { mSmsColId = c.getColumnIndex(BaseColumns._ID); mSmsColFolder = c.getColumnIndex(Sms.TYPE); mSmsColRead = c.getColumnIndex(Sms.READ); mSmsColSubject = c.getColumnIndex(Sms.BODY); mSmsColAddress = c.getColumnIndex(Sms.ADDRESS); mSmsColDate = c.getColumnIndex(Sms.DATE); mSmsColType = c.getColumnIndex(Sms.TYPE); mSmsColThreadId = c.getColumnIndex(Sms.THREAD_ID); } public void setMmsColumns(Cursor c) { mMmsColId = c.getColumnIndex(BaseColumns._ID); mMmsColFolder = c.getColumnIndex(Mms.MESSAGE_BOX); mMmsColRead = c.getColumnIndex(Mms.READ); mMmsColAttachmentSize = c.getColumnIndex(Mms.MESSAGE_SIZE); mMmsColTextOnly = c.getColumnIndex(Mms.TEXT_ONLY); mMmsColSize = c.getColumnIndex(Mms.MESSAGE_SIZE); mMmsColDate = c.getColumnIndex(Mms.DATE); mMmsColSubject = c.getColumnIndex(Mms.SUBJECT); mMmsColThreadId = c.getColumnIndex(Mms.THREAD_ID); } } public BluetoothMapContent(final Context context, BluetoothMapAccountItem account, BluetoothMapMasInstance mas) { mContext = context; mResolver = mContext.getContentResolver(); mMasInstance = mas; if (mResolver == null) { if (D) { Log.d(TAG, "getContentResolver failed"); } } if (account != null) { mBaseUri = account.mBase_uri + "/"; mAccount = account; } else { mBaseUri = null; mAccount = null; } } private static void close(Closeable c) { try { if (c != null) { c.close(); } } catch (IOException e) { } } private void setProtected(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_PROTECTED) != 0) { String protect = "no"; if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { int flagProtected = c.getInt(fi.mMessageColProtected); if (flagProtected == 1) { protect = "yes"; } } if (V) { Log.d(TAG, "setProtected: " + protect + "\n"); } e.setProtect(protect); } } private void setThreadId(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_CONVERSATION_ID) != 0) { long threadId = 0; TYPE type = TYPE.SMS_GSM; // Just used for handle encoding if (fi.mMsgType == FilterInfo.TYPE_SMS) { threadId = c.getLong(fi.mSmsColThreadId); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { threadId = c.getLong(fi.mMmsColThreadId); type = TYPE.MMS; // Just used for handle encoding } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { threadId = c.getLong(fi.mMessageColThreadId); type = TYPE.EMAIL; // Just used for handle encoding } e.setThreadId(threadId, type); if (V) { Log.d(TAG, "setThreadId: " + threadId + "\n"); } } } private void setThreadName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { // TODO: Maybe this should be valid for SMS/MMS if ((ap.getParameterMask() & MASK_CONVERSATION_NAME) != 0) { if (fi.mMsgType == FilterInfo.TYPE_IM) { String threadName = c.getString(fi.mMessageColThreadName); e.setThreadName(threadName); if (V) { Log.d(TAG, "setThreadName: " + threadName + "\n"); } } } } private void setSent(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_SENT) != 0) { int msgType = 0; if (fi.mMsgType == FilterInfo.TYPE_SMS) { msgType = c.getInt(fi.mSmsColFolder); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { msgType = c.getInt(fi.mMmsColFolder); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { msgType = c.getInt(fi.mMessageColFolder); } String sent = null; if (msgType == 2) { sent = "yes"; } else { sent = "no"; } if (V) { Log.d(TAG, "setSent: " + sent); } e.setSent(sent); } } private void setRead(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { int read = 0; if (fi.mMsgType == FilterInfo.TYPE_SMS) { read = c.getInt(fi.mSmsColRead); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { read = c.getInt(fi.mMmsColRead); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { read = c.getInt(fi.mMessageColRead); } String setread = null; if (V) { Log.d(TAG, "setRead: " + setread); } e.setRead((read == 1), ((ap.getParameterMask() & MASK_READ) != 0)); } private void setConvoRead(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { String setread = null; int read = 0; read = c.getInt(fi.mConvoColRead); if (V) { Log.d(TAG, "setRead: " + setread); } e.setRead((read == 1), ((ap.getParameterMask() & MASK_READ) != 0)); } private void setPriority(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_PRIORITY) != 0) { String priority = "no"; if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { int highPriority = c.getInt(fi.mMessageColPriority); if (highPriority == 1) { priority = "yes"; } } int pri = 0; if (fi.mMsgType == FilterInfo.TYPE_MMS) { pri = c.getInt(c.getColumnIndex(Mms.PRIORITY)); } if (pri == PduHeaders.PRIORITY_HIGH) { priority = "yes"; } if (V) { Log.d(TAG, "setPriority: " + priority); } e.setPriority(priority); } } /** * For SMS we set the attachment size to 0, as all data will be text data, hence * attachments for SMS is not possible. * For MMS all data is actually attachments, hence we do set the attachment size to * the total message size. To provide a more accurate attachment size, one could * extract the length (in bytes) of the text parts. */ private void setAttachment(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) { int size = 0; String attachmentMimeTypes = null; if (fi.mMsgType == FilterInfo.TYPE_MMS) { if (c.getInt(fi.mMmsColTextOnly) == 0) { size = c.getInt(fi.mMmsColAttachmentSize); if (size <= 0) { // We know there are attachments, since it is not TextOnly // Hence the size in the database must be wrong. // Set size to 1 to indicate to the client, that attachments are present if (D) { Log.d(TAG, "Error in message database, size reported as: " + size + " Changing size to 1"); } size = 1; } // TODO: Add handling of attachemnt mime types } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { int attachment = c.getInt(fi.mMessageColAttachment); size = c.getInt(fi.mMessageColAttachmentSize); if (attachment == 1 && size == 0) { if (D) { Log.d(TAG, "Error in message database, attachment size reported as: " + size + " Changing size to 1"); } size = 1; /* Ensure we indicate we have attachments in the size, if the message has attachments, in case the e-mail client do not report a size */ } } else if (fi.mMsgType == FilterInfo.TYPE_IM) { int attachment = c.getInt(fi.mMessageColAttachment); size = c.getInt(fi.mMessageColAttachmentSize); if (attachment == 1 && size == 0) { size = 1; /* Ensure we indicate we have attachments in the size, it the message has attachments, in case the e-mail client do not report a size */ attachmentMimeTypes = c.getString(fi.mMessageColAttachmentMime); } } if (V) { Log.d(TAG, "setAttachmentSize: " + size + "\n" + "setAttachmentMimeTypes: " + attachmentMimeTypes); } e.setAttachmentSize(size); if ((mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10) && ( (ap.getParameterMask() & MASK_ATTACHMENT_MIME) != 0)) { e.setAttachmentMimeTypes(attachmentMimeTypes); } } } private void setText(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_TEXT) != 0) { String hasText = ""; if (fi.mMsgType == FilterInfo.TYPE_SMS) { hasText = "yes"; } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { int textOnly = c.getInt(fi.mMmsColTextOnly); if (textOnly == 1) { hasText = "yes"; } else { long id = c.getLong(fi.mMmsColId); String text = getTextPartsMms(mResolver, id); if (text != null && text.length() > 0) { hasText = "yes"; } else { hasText = "no"; } } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { hasText = "yes"; } if (V) { Log.d(TAG, "setText: " + hasText); } e.setText(hasText); } } private void setReceptionStatus(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_RECEPTION_STATUS) != 0) { String status = "complete"; if (V) { Log.d(TAG, "setReceptionStatus: " + status); } e.setReceptionStatus(status); } } private void setDeliveryStatus(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_DELIVERY_STATUS) != 0) { String deliveryStatus = "delivered"; // TODO: Should be handled for SMS and MMS as well if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { deliveryStatus = c.getString(fi.mMessageColDelivery); } if (V) { Log.d(TAG, "setDeliveryStatus: " + deliveryStatus); } e.setDeliveryStatus(deliveryStatus); } } private void setSize(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_SIZE) != 0) { int size = 0; if (fi.mMsgType == FilterInfo.TYPE_SMS) { String subject = c.getString(fi.mSmsColSubject); size = subject.length(); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { size = c.getInt(fi.mMmsColSize); //MMS complete size = attachment_size + subject length String subject = e.getSubject(); if (subject == null || subject.length() == 0) { // Handle setSubject if not done case setSubject(e, c, fi, ap); } if (subject != null && subject.length() != 0) { size += subject.length(); } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { size = c.getInt(fi.mMessageColSize); } if (size <= 0) { // A message cannot have size 0 // Hence the size in the database must be wrong. // Set size to 1 to indicate to the client, that the message has content. if (D) { Log.d(TAG, "Error in message database, size reported as: " + size + " Changing size to 1"); } size = 1; } if (V) { Log.d(TAG, "setSize: " + size); } e.setSize(size); } } private TYPE getType(Cursor c, FilterInfo fi) { TYPE type = null; if (V) { Log.d(TAG, "getType: for filterMsgType" + fi.mMsgType); } if (fi.mMsgType == FilterInfo.TYPE_SMS) { if (V) { Log.d(TAG, "getType: phoneType for SMS " + fi.mPhoneType); } if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_CDMA) { type = TYPE.SMS_CDMA; } else { type = TYPE.SMS_GSM; } } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { type = TYPE.MMS; } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { type = TYPE.EMAIL; } else if (fi.mMsgType == FilterInfo.TYPE_IM) { type = TYPE.IM; } if (V) { Log.d(TAG, "getType: " + type); } return type; } private void setFolderType(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_FOLDER_TYPE) != 0) { String folderType = null; int folderId = 0; if (fi.mMsgType == FilterInfo.TYPE_SMS) { folderId = c.getInt(fi.mSmsColFolder); if (folderId == 1) { folderType = BluetoothMapContract.FOLDER_NAME_INBOX; } else if (folderId == 2) { folderType = BluetoothMapContract.FOLDER_NAME_SENT; } else if (folderId == 3) { folderType = BluetoothMapContract.FOLDER_NAME_DRAFT; } else if (folderId == 4 || folderId == 5 || folderId == 6) { folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX; } else { folderType = BluetoothMapContract.FOLDER_NAME_DELETED; } } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { folderId = c.getInt(fi.mMmsColFolder); if (folderId == 1) { folderType = BluetoothMapContract.FOLDER_NAME_INBOX; } else if (folderId == 2) { folderType = BluetoothMapContract.FOLDER_NAME_SENT; } else if (folderId == 3) { folderType = BluetoothMapContract.FOLDER_NAME_DRAFT; } else if (folderId == 4) { folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX; } else { folderType = BluetoothMapContract.FOLDER_NAME_DELETED; } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { // TODO: need to find name from id and then set folder type } else if (fi.mMsgType == FilterInfo.TYPE_IM) { folderId = c.getInt(fi.mMessageColFolder); if (folderId == BluetoothMapContract.FOLDER_ID_INBOX) { folderType = BluetoothMapContract.FOLDER_NAME_INBOX; } else if (folderId == BluetoothMapContract.FOLDER_ID_SENT) { folderType = BluetoothMapContract.FOLDER_NAME_SENT; } else if (folderId == BluetoothMapContract.FOLDER_ID_DRAFT) { folderType = BluetoothMapContract.FOLDER_NAME_DRAFT; } else if (folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) { folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX; } else if (folderId == BluetoothMapContract.FOLDER_ID_DELETED) { folderType = BluetoothMapContract.FOLDER_NAME_DELETED; } else { folderType = BluetoothMapContract.FOLDER_NAME_OTHER; } } if (V) { Log.d(TAG, "setFolderType: " + folderType); } e.setFolderType(folderType); } } private String getRecipientNameEmail(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi) { String toAddress, ccAddress, bccAddress; toAddress = c.getString(fi.mMessageColToAddress); ccAddress = c.getString(fi.mMessageColCcAddress); bccAddress = c.getString(fi.mMessageColBccAddress); StringBuilder sb = new StringBuilder(); if (toAddress != null) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(toAddress); if (tokens.length != 0) { if (D) { Log.d(TAG, "toName count= " + tokens.length); } int i = 0; boolean first = true; while (i < tokens.length) { if (V) { Log.d(TAG, "ToName = " + tokens[i].toString()); } String name = tokens[i].getName(); if (!first) { sb.append("; "); //Delimiter } sb.append(name); first = false; i++; } } if (ccAddress != null) { sb.append("; "); } } if (ccAddress != null) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(ccAddress); if (tokens.length != 0) { if (D) { Log.d(TAG, "ccName count= " + tokens.length); } int i = 0; boolean first = true; while (i < tokens.length) { if (V) { Log.d(TAG, "ccName = " + tokens[i].toString()); } String name = tokens[i].getName(); if (!first) { sb.append("; "); //Delimiter } sb.append(name); first = false; i++; } } if (bccAddress != null) { sb.append("; "); } } if (bccAddress != null) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(bccAddress); if (tokens.length != 0) { if (D) { Log.d(TAG, "bccName count= " + tokens.length); } int i = 0; boolean first = true; while (i < tokens.length) { if (V) { Log.d(TAG, "bccName = " + tokens[i].toString()); } String name = tokens[i].getName(); if (!first) { sb.append("; "); //Delimiter } sb.append(name); first = false; i++; } } } return sb.toString(); } private String getRecipientAddressingEmail(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi) { String toAddress, ccAddress, bccAddress; toAddress = c.getString(fi.mMessageColToAddress); ccAddress = c.getString(fi.mMessageColCcAddress); bccAddress = c.getString(fi.mMessageColBccAddress); StringBuilder sb = new StringBuilder(); if (toAddress != null) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(toAddress); if (tokens.length != 0) { if (D) { Log.d(TAG, "toAddress count= " + tokens.length); } int i = 0; boolean first = true; while (i < tokens.length) { if (V) { Log.d(TAG, "ToAddress = " + tokens[i].toString()); } String email = tokens[i].getAddress(); if (!first) { sb.append("; "); //Delimiter } sb.append(email); first = false; i++; } } if (ccAddress != null) { sb.append("; "); } } if (ccAddress != null) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(ccAddress); if (tokens.length != 0) { if (D) { Log.d(TAG, "ccAddress count= " + tokens.length); } int i = 0; boolean first = true; while (i < tokens.length) { if (V) { Log.d(TAG, "ccAddress = " + tokens[i].toString()); } String email = tokens[i].getAddress(); if (!first) { sb.append("; "); //Delimiter } sb.append(email); first = false; i++; } } if (bccAddress != null) { sb.append("; "); } } if (bccAddress != null) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(bccAddress); if (tokens.length != 0) { if (D) { Log.d(TAG, "bccAddress count= " + tokens.length); } int i = 0; boolean first = true; while (i < tokens.length) { if (V) { Log.d(TAG, "bccAddress = " + tokens[i].toString()); } String email = tokens[i].getAddress(); if (!first) { sb.append("; "); //Delimiter } sb.append(email); first = false; i++; } } } return sb.toString(); } private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_RECIPIENT_ADDRESSING) != 0) { String address = null; if (fi.mMsgType == FilterInfo.TYPE_SMS) { int msgType = c.getInt(fi.mSmsColType); if (msgType == Sms.MESSAGE_TYPE_INBOX) { address = fi.mPhoneNum; } else { address = c.getString(c.getColumnIndex(Sms.ADDRESS)); } if ((address == null) && msgType == Sms.MESSAGE_TYPE_DRAFT) { // Fetch address for Drafts folder from "canonical_address" table int threadIdInd = c.getColumnIndex(Sms.THREAD_ID); String threadIdStr = c.getString(threadIdInd); // If a draft message has no recipient, it has no thread ID // hence threadIdStr could possibly be null if (threadIdStr != null) { address = getCanonicalAddressSms(mResolver, Integer.valueOf(threadIdStr)); } if (V) { Log.v(TAG, "threadId = " + threadIdStr + " adress:" + address + "\n"); } } } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); address = getAddressMms(mResolver, id, MMS_TO); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { /* Might be another way to handle addresses */ address = getRecipientAddressingEmail(e, c, fi); } if (V) { Log.v(TAG, "setRecipientAddressing: " + address); } if (address == null) { address = ""; } e.setRecipientAddressing(address); } } private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) { String name = null; if (fi.mMsgType == FilterInfo.TYPE_SMS) { int msgType = c.getInt(fi.mSmsColType); if (msgType != 1) { String phone = c.getString(fi.mSmsColAddress); if (phone != null && !phone.isEmpty()) { name = getContactNameFromPhone(phone, mResolver); } } else { name = fi.mPhoneAlphaTag; } } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { long id = c.getLong(fi.mMmsColId); String phone; if (e.getRecipientAddressing() != null) { phone = getAddressMms(mResolver, id, MMS_TO); } else { phone = e.getRecipientAddressing(); } if (phone != null && !phone.isEmpty()) { name = getContactNameFromPhone(phone, mResolver); } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { /* Might be another way to handle address and names */ name = getRecipientNameEmail(e, c, fi); } if (V) { Log.v(TAG, "setRecipientName: " + name); } if (name == null) { name = ""; } e.setRecipientName(name); } } private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) { String address = ""; String tempAddress; if (fi.mMsgType == FilterInfo.TYPE_SMS) { int msgType = c.getInt(fi.mSmsColType); if (msgType == 1) { // INBOX tempAddress = c.getString(fi.mSmsColAddress); } else { tempAddress = fi.mPhoneNum; } if (tempAddress == null) { /* This can only happen on devices with no SIM - hence will typically not have any SMS messages. */ } else { address = PhoneNumberUtils.extractNetworkPortion(tempAddress); /* extractNetworkPortion can return N if the number is a service "number" = * a string with the a name in (i.e. "Some-Tele-company" would return N * because of the N in compaNy) * Hence we need to check if the number is actually a string with alpha chars. * */ Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress) .matches("[0-9]*[a-zA-Z]+[0-9]*"); if (address == null || address.length() < 2 || alpha) { address = tempAddress; // if the number is a service acsii text just use it } } } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { long id = c.getLong(fi.mMmsColId); tempAddress = getAddressMms(mResolver, id, MMS_FROM); address = PhoneNumberUtils.extractNetworkPortion(tempAddress); if (address == null || address.length() < 1) { address = tempAddress; // if the number is a service acsii text just use it } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* || fi.mMsgType == FilterInfo.TYPE_IM*/) { String nameEmail = c.getString(fi.mMessageColFromAddress); Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail); if (tokens.length != 0) { if (D) { Log.d(TAG, "Originator count= " + tokens.length); } int i = 0; boolean first = true; while (i < tokens.length) { if (V) { Log.d(TAG, "SenderAddress = " + tokens[i].toString()); } String[] emails = new String[1]; emails[0] = tokens[i].getAddress(); String name = tokens[i].getName(); if (!first) { address += "; "; //Delimiter } address += emails[0]; first = false; i++; } } } else if (fi.mMsgType == FilterInfo.TYPE_IM) { // TODO: For IM we add the contact ID in the addressing long contactId = c.getLong(fi.mMessageColFromAddress); // TODO: This is a BAD hack, that we map the contact ID to a conversation ID!!! // We need to reach a conclusion on what to do Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); Cursor contacts = mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION, BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = " + contactId, null, null); try { // TODO this will not work for group-chats if (contacts != null && contacts.moveToFirst()) { address = contacts.getString(contacts.getColumnIndex( BluetoothMapContract.ConvoContactColumns.UCI)); } } finally { if (contacts != null) { contacts.close(); } } } if (V) { Log.v(TAG, "setSenderAddressing: " + address); } if (address == null) { address = ""; } e.setSenderAddressing(address); } } private void setSenderName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) { String name = ""; if (fi.mMsgType == FilterInfo.TYPE_SMS) { int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); if (msgType == 1) { String phone = c.getString(fi.mSmsColAddress); if (phone != null && !phone.isEmpty()) { name = getContactNameFromPhone(phone, mResolver); } } else { name = fi.mPhoneAlphaTag; } } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { long id = c.getLong(fi.mMmsColId); String phone; if (e.getSenderAddressing() != null) { phone = getAddressMms(mResolver, id, MMS_FROM); } else { phone = e.getSenderAddressing(); } if (phone != null && !phone.isEmpty()) { name = getContactNameFromPhone(phone, mResolver); } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* || fi.mMsgType == FilterInfo.TYPE_IM*/) { String nameEmail = c.getString(fi.mMessageColFromAddress); Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail); if (tokens.length != 0) { if (D) { Log.d(TAG, "Originator count= " + tokens.length); } int i = 0; boolean first = true; while (i < tokens.length) { if (V) { Log.d(TAG, "senderName = " + tokens[i].toString()); } String[] emails = new String[1]; emails[0] = tokens[i].getAddress(); String nameIn = tokens[i].getName(); if (!first) { name += "; "; //Delimiter } name += nameIn; first = false; i++; } } } else if (fi.mMsgType == FilterInfo.TYPE_IM) { // For IM we add the contact ID in the addressing long contactId = c.getLong(fi.mMessageColFromAddress); Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); Cursor contacts = mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION, BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = " + contactId, null, null); try { // TODO this will not work for group-chats if (contacts != null && contacts.moveToFirst()) { name = contacts.getString(contacts.getColumnIndex( BluetoothMapContract.ConvoContactColumns.NAME)); } } finally { if (contacts != null) { contacts.close(); } } } if (V) { Log.v(TAG, "setSenderName: " + name); } if (name == null) { name = ""; } e.setSenderName(name); } } private void setDateTime(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_DATETIME) != 0) { long date = 0; if (fi.mMsgType == FilterInfo.TYPE_SMS) { date = c.getLong(fi.mSmsColDate); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { /* Use Mms.DATE for all messages. Although contract class states */ /* Mms.DATE_SENT are for outgoing messages. But that is not working. */ date = c.getLong(fi.mMmsColDate) * 1000L; /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */ /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */ /* date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */ /* } else { */ /* date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */ /* } */ } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { date = c.getLong(fi.mMessageColDate); } e.setDateTime(date); } } private void setLastActivity(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { long date = 0; if (fi.mMsgType == FilterInfo.TYPE_SMS || fi.mMsgType == FilterInfo.TYPE_MMS) { date = c.getLong(MMS_SMS_THREAD_COL_DATE); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { date = c.getLong(fi.mConvoColLastActivity); } e.setLastActivity(date); if (V) { Log.v(TAG, "setDateTime: " + e.getLastActivityString()); } } public static String getTextPartsMms(ContentResolver r, long id) { String text = ""; String selection = new String("mid=" + id); String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part"); Uri uriAddress = Uri.parse(uriStr); // TODO: maybe use a projection with only "ct" and "text" Cursor c = r.query(uriAddress, null, selection, null, null); try { if (c != null && c.moveToFirst()) { do { String ct = c.getString(c.getColumnIndex("ct")); if (ct.equals("text/plain")) { String part = c.getString(c.getColumnIndex("text")); if (part != null) { text += part; } } } while (c.moveToNext()); } } finally { if (c != null) { c.close(); } } return text; } private void setSubject(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { String subject = ""; int subLength = ap.getSubjectLength(); if (subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { subLength = 256; } // Fix Subject Display issue with HONDA Carkit - Ignore subject Mask. if (DeviceWorkArounds.addressStartsWith(BluetoothMapService.getRemoteDevice().getAddress(), DeviceWorkArounds.HONDA_CARKIT) || (ap.getParameterMask() & MASK_SUBJECT) != 0) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { subject = c.getString(fi.mSmsColSubject); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { subject = c.getString(fi.mMmsColSubject); if (subject == null || subject.length() == 0) { /* Get subject from mms text body parts - if any exists */ long id = c.getLong(fi.mMmsColId); subject = getTextPartsMms(mResolver, id); } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { subject = c.getString(fi.mMessageColSubject); } if (subject != null && subject.length() > subLength) { subject = subject.substring(0, subLength); } else if (subject == null) { subject = ""; } if (V) { Log.d(TAG, "setSubject: " + subject); } e.setSubject(subject); } } private void setHandle(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { long handle = -1; if (fi.mMsgType == FilterInfo.TYPE_SMS) { handle = c.getLong(fi.mSmsColId); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { handle = c.getLong(fi.mMmsColId); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { handle = c.getLong(fi.mMessageColId); } if (V) { Log.d(TAG, "setHandle: " + handle); } e.setHandle(handle); } private BluetoothMapMessageListingElement element(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement(); setHandle(e, c, fi, ap); setDateTime(e, c, fi, ap); e.setType(getType(c, fi), (ap.getParameterMask() & MASK_TYPE) != 0); setRead(e, c, fi, ap); // we set number and name for sender/recipient later // they require lookup on contacts so no need to // do it for all elements unless they are to be used. e.setCursorIndex(c.getPosition()); return e; } private BluetoothMapConvoListingElement createConvoElement(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { BluetoothMapConvoListingElement e = new BluetoothMapConvoListingElement(); setLastActivity(e, c, fi, ap); e.setType(getType(c, fi)); // setConvoRead(e, c, fi, ap); e.setCursorIndex(c.getPosition()); return e; } /* TODO: Change to use SmsMmsContacts.getContactNameFromPhone() with proper use of * caching. */ public static String getContactNameFromPhone(String phone, ContentResolver resolver) { String name = null; //Handle possible exception for empty phone address if (TextUtils.isEmpty(phone)) { return name; } Uri uri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone)); String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME}; String selection = Contacts.IN_VISIBLE_GROUP + "=1"; String orderBy = Contacts.DISPLAY_NAME + " ASC"; Cursor c = null; try { c = resolver.query(uri, projection, selection, null, orderBy); if (c != null) { int colIndex = c.getColumnIndex(Contacts.DISPLAY_NAME); if (c.getCount() >= 1) { c.moveToFirst(); name = c.getString(colIndex); } } } finally { if (c != null) { c.close(); } } return name; } private static final String[] RECIPIENT_ID_PROJECTION = {Threads.RECIPIENT_IDS}; /** * Get SMS RecipientAddresses for DRAFT folder based on threadId * */ public static String getCanonicalAddressSms(ContentResolver r, int threadId) { /* 1. Get Recipient Ids from Threads.CONTENT_URI 2. Get Recipient Address for corresponding Id from canonical-addresses table. */ //Uri sAllCanonical = Uri.parse("content://mms-sms/canonical-addresses"); Uri sAllCanonical = MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build(); Uri sAllThreadsUri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); Cursor cr = null; String recipientAddress = ""; String recipientIds = null; String whereClause = "_id=" + threadId; if (V) { Log.v(TAG, "whereClause is " + whereClause); } try { cr = r.query(sAllThreadsUri, RECIPIENT_ID_PROJECTION, whereClause, null, null); if (cr != null && cr.moveToFirst()) { recipientIds = cr.getString(0); if (V) { Log.v(TAG, "cursor.getCount(): " + cr.getCount() + "recipientIds: " + recipientIds + "selection: " + whereClause); } } } finally { if (cr != null) { cr.close(); cr = null; } } if (V) { Log.v(TAG, "recipientIds with spaces: " + recipientIds + "\n"); } if (recipientIds != null) { String[] recipients = null; whereClause = ""; recipients = recipientIds.split(" "); for (String id : recipients) { if (whereClause.length() != 0) { whereClause += " OR "; } whereClause += "_id=" + id; } if (V) { Log.v(TAG, "whereClause is " + whereClause); } try { cr = r.query(sAllCanonical, null, whereClause, null, null); if (cr != null && cr.moveToFirst()) { do { //TODO: Multiple Recipeints are appended with ";" for now. if (recipientAddress.length() != 0) { recipientAddress += ";"; } recipientAddress += cr.getString(cr.getColumnIndex(CanonicalAddressesColumns.ADDRESS)); } while (cr.moveToNext()); } } finally { if (cr != null) { cr.close(); } } } if (V) { Log.v(TAG, "Final recipientAddress : " + recipientAddress); } return recipientAddress; } public static String getAddressMms(ContentResolver r, long id, int type) { String selection = new String("msg_id=" + id + " AND type=" + type); String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr"); Uri uriAddress = Uri.parse(uriStr); String addr = null; String[] projection = {Mms.Addr.ADDRESS}; Cursor c = null; try { c = r.query(uriAddress, projection, selection, null, null); // TODO: Add projection int colIndex = c.getColumnIndex(Mms.Addr.ADDRESS); if (c != null) { if (c.moveToFirst()) { addr = c.getString(colIndex); if (addr.equals(INSERT_ADDRES_TOKEN)) { addr = ""; } } } } finally { if (c != null) { c.close(); } } return addr; } /** * Matching functions for originator and recipient for MMS * @return true if found a match */ private boolean matchRecipientMms(Cursor c, FilterInfo fi, String recip) { boolean res; long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); String phone = getAddressMms(mResolver, id, MMS_TO); if (phone != null && phone.length() > 0) { if (phone.matches(recip)) { if (V) { Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone); } res = true; } else { String name = getContactNameFromPhone(phone, mResolver); if (name != null && name.length() > 0 && name.matches(recip)) { if (V) { Log.v(TAG, "matchRecipientMms: match recipient name = " + name); } res = true; } else { res = false; } } } else { res = false; } return res; } private boolean matchRecipientSms(Cursor c, FilterInfo fi, String recip) { boolean res; int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); if (msgType == 1) { String phone = fi.mPhoneNum; String name = fi.mPhoneAlphaTag; if (phone != null && phone.length() > 0 && phone.matches(recip)) { if (V) { Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone); } res = true; } else if (name != null && name.length() > 0 && name.matches(recip)) { if (V) { Log.v(TAG, "matchRecipientSms: match recipient name = " + name); } res = true; } else { res = false; } } else { String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); if (phone != null && phone.length() > 0) { if (phone.matches(recip)) { if (V) { Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone); } res = true; } else { String name = getContactNameFromPhone(phone, mResolver); if (name != null && name.length() > 0 && name.matches(recip)) { if (V) { Log.v(TAG, "matchRecipientSms: match recipient name = " + name); } res = true; } else { res = false; } } } else { res = false; } } return res; } private boolean matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { boolean res; String recip = ap.getFilterRecipient(); if (recip != null && recip.length() > 0) { recip = recip.replace("*", ".*"); recip = ".*" + recip + ".*"; if (fi.mMsgType == FilterInfo.TYPE_SMS) { res = matchRecipientSms(c, fi, recip); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { res = matchRecipientMms(c, fi, recip); } else { if (D) { Log.d(TAG, "matchRecipient: Unknown msg type: " + fi.mMsgType); } res = false; } } else { res = true; } return res; } private boolean matchOriginatorMms(Cursor c, FilterInfo fi, String orig) { boolean res; long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); String phone = getAddressMms(mResolver, id, MMS_FROM); if (phone != null && phone.length() > 0) { if (phone.matches(orig)) { if (V) { Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone); } res = true; } else { String name = getContactNameFromPhone(phone, mResolver); if (name != null && name.length() > 0 && name.matches(orig)) { if (V) { Log.v(TAG, "matchOriginatorMms: match originator name = " + name); } res = true; } else { res = false; } } } else { res = false; } return res; } private boolean matchOriginatorSms(Cursor c, FilterInfo fi, String orig) { boolean res; int msgType = c.getInt(c.getColumnIndex(Sms.TYPE)); if (msgType == 1) { String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); if (phone != null && phone.length() > 0) { if (phone.matches(orig)) { if (V) { Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone); } res = true; } else { String name = getContactNameFromPhone(phone, mResolver); if (name != null && name.length() > 0 && name.matches(orig)) { if (V) { Log.v(TAG, "matchOriginatorSms: match originator name = " + name); } res = true; } else { res = false; } } } else { res = false; } } else { String phone = fi.mPhoneNum; String name = fi.mPhoneAlphaTag; if (phone != null && phone.length() > 0 && phone.matches(orig)) { if (V) { Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone); } res = true; } else if (name != null && name.length() > 0 && name.matches(orig)) { if (V) { Log.v(TAG, "matchOriginatorSms: match originator name = " + name); } res = true; } else { res = false; } } return res; } private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { boolean res; String orig = ap.getFilterOriginator(); if (orig != null && orig.length() > 0) { orig = orig.replace("*", ".*"); orig = ".*" + orig + ".*"; if (fi.mMsgType == FilterInfo.TYPE_SMS) { res = matchOriginatorSms(c, fi, orig); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { res = matchOriginatorMms(c, fi, orig); } else { if (D) { Log.d(TAG, "matchOriginator: Unknown msg type: " + fi.mMsgType); } res = false; } } else { res = true; } return res; } private boolean matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { return matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap); } /* * Where filter functions * */ private String setWhereFilterFolderTypeSms(String folder) { String where = ""; if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) { where = Sms.TYPE + " = 1 AND " + Sms.THREAD_ID + " <> -1"; } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) { where = "(" + Sms.TYPE + " = 4 OR " + Sms.TYPE + " = 5 OR " + Sms.TYPE + " = 6) AND " + Sms.THREAD_ID + " <> -1"; } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) { where = Sms.TYPE + " = 2 AND " + Sms.THREAD_ID + " <> -1"; } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) { where = Sms.TYPE + " = 3 AND " + "(" + Sms.THREAD_ID + " IS NULL OR " + Sms.THREAD_ID + " <> -1 )"; } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) { where = Sms.THREAD_ID + " = -1"; } return where; } private String setWhereFilterFolderTypeMms(String folder) { String where = ""; if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) { where = Mms.MESSAGE_BOX + " = 1 AND " + Mms.THREAD_ID + " <> -1"; } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) { where = Mms.MESSAGE_BOX + " = 4 AND " + Mms.THREAD_ID + " <> -1"; } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) { where = Mms.MESSAGE_BOX + " = 2 AND " + Mms.THREAD_ID + " <> -1"; } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) { where = Mms.MESSAGE_BOX + " = 3 AND " + "(" + Mms.THREAD_ID + " IS NULL OR " + Mms.THREAD_ID + " <> -1 )"; } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) { where = Mms.THREAD_ID + " = -1"; } return where; } private String setWhereFilterFolderTypeEmail(long folderId) { String where = ""; if (folderId >= 0) { where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId; } else { Log.e(TAG, "setWhereFilterFolderTypeEmail: not valid!"); throw new IllegalArgumentException("Invalid folder ID"); } return where; } private String setWhereFilterFolderTypeIm(long folderId) { String where = ""; if (folderId > BluetoothMapContract.FOLDER_ID_OTHER) { where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId; } else { Log.e(TAG, "setWhereFilterFolderTypeIm: not valid!"); throw new IllegalArgumentException("Invalid folder ID"); } return where; } private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement, FilterInfo fi) { String where = "1=1"; if (!folderElement.shouldIgnore()) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { where = setWhereFilterFolderTypeSms(folderElement.getName()); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { where = setWhereFilterFolderTypeMms(folderElement.getName()); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { where = setWhereFilterFolderTypeEmail(folderElement.getFolderId()); } else if (fi.mMsgType == FilterInfo.TYPE_IM) { where = setWhereFilterFolderTypeIm(folderElement.getFolderId()); } } return where; } private String setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi) { String where = ""; if (ap.getFilterReadStatus() != -1) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { if ((ap.getFilterReadStatus() & 0x01) != 0) { where = " AND " + Sms.READ + "= 0"; } if ((ap.getFilterReadStatus() & 0x02) != 0) { where = " AND " + Sms.READ + "= 1"; } } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { if ((ap.getFilterReadStatus() & 0x01) != 0) { where = " AND " + Mms.READ + "= 0"; } if ((ap.getFilterReadStatus() & 0x02) != 0) { where = " AND " + Mms.READ + "= 1"; } } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { if ((ap.getFilterReadStatus() & 0x01) != 0) { where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0"; } if ((ap.getFilterReadStatus() & 0x02) != 0) { where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 1"; } } } return where; } private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) { String where = ""; if ((ap.getFilterPeriodBegin() != -1)) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin(); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { where = " AND " + BluetoothMapContract.MessageColumns.DATE + " >= " + (ap.getFilterPeriodBegin()); } } if ((ap.getFilterPeriodEnd() != -1)) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd(); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { where += " AND " + BluetoothMapContract.MessageColumns.DATE + " < " + (ap.getFilterPeriodEnd()); } } return where; } private String setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi) { String where = ""; if ((ap.getFilterLastActivityBegin() != -1)) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { where = " AND " + Sms.DATE + " >= " + ap.getFilterLastActivityBegin(); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { where = " AND " + Mms.DATE + " >= " + (ap.getFilterLastActivityBegin() / 1000L); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { where = " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY + " >= " + (ap.getFilterPeriodBegin()); } } if ((ap.getFilterLastActivityEnd() != -1)) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { where += " AND " + Sms.DATE + " < " + ap.getFilterLastActivityEnd(); } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L); } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { where += " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY + " < " + (ap.getFilterLastActivityEnd()); } } return where; } private String setWhereFilterOriginatorEmail(BluetoothMapAppParams ap) { String where = ""; String orig = ap.getFilterOriginator(); /* Be aware of wild cards in the beginning of string, may not be valid? */ if (orig != null && orig.length() > 0) { orig = orig.replace("*", "%"); where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST + " LIKE '%" + orig + "%'"; } return where; } private String setWhereFilterOriginatorIM(BluetoothMapAppParams ap) { String where = ""; String orig = ap.getFilterOriginator(); /* Be aware of wild cards in the beginning of string, may not be valid? */ if (orig != null && orig.length() > 0) { orig = orig.replace("*", "%"); where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST + " LIKE '%" + orig + "%'"; } return where; } private String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) { String where = ""; int pri = ap.getFilterPriority(); /*only MMS have priority info */ if (fi.mMsgType == FilterInfo.TYPE_MMS) { if (pri == 0x0002) { where += " AND " + Mms.PRIORITY + "<=" + Integer.toString( PduHeaders.PRIORITY_NORMAL); } else if (pri == 0x0001) { where += " AND " + Mms.PRIORITY + "=" + Integer.toString(PduHeaders.PRIORITY_HIGH); } } if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { if (pri == 0x0002) { where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "!=1"; } else if (pri == 0x0001) { where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "=1"; } } // TODO: no priority filtering in IM return where; } private String setWhereFilterRecipientEmail(BluetoothMapAppParams ap) { String where = ""; String recip = ap.getFilterRecipient(); /* Be aware of wild cards in the beginning of string, may not be valid? */ if (recip != null && recip.length() > 0) { recip = recip.replace("*", "%"); where = " AND (" + BluetoothMapContract.MessageColumns.TO_LIST + " LIKE '%" + recip + "%' OR " + BluetoothMapContract.MessageColumns.CC_LIST + " LIKE '%" + recip + "%' OR " + BluetoothMapContract.MessageColumns.BCC_LIST + " LIKE '%" + recip + "%' )"; } return where; } private String setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi) { String where = ""; long id = -1; String msgHandle = ap.getFilterMsgHandleString(); if (msgHandle != null) { id = BluetoothMapUtils.getCpHandle(msgHandle); if (D) { Log.d(TAG, "id: " + id); } } if (id != -1) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { where = " AND " + Sms._ID + " = " + id; } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { where = " AND " + Mms._ID + " = " + id; } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { where = " AND " + BluetoothMapContract.MessageColumns._ID + " = " + id; } } return where; } private String setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi) { String where = ""; long id = -1; String msgHandle = ap.getFilterConvoIdString(); if (msgHandle != null) { id = BluetoothMapUtils.getMsgHandleAsLong(msgHandle); if (D) { Log.d(TAG, "id: " + id); } } if (id > 0) { if (fi.mMsgType == FilterInfo.TYPE_SMS) { where = " AND " + Sms.THREAD_ID + " = " + id; } else if (fi.mMsgType == FilterInfo.TYPE_MMS) { where = " AND " + Mms.THREAD_ID + " = " + id; } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) { where = " AND " + BluetoothMapContract.MessageColumns.THREAD_ID + " = " + id; } } return where; } private String setWhereFilter(BluetoothMapFolderElement folderElement, FilterInfo fi, BluetoothMapAppParams ap) { String where = ""; where += setWhereFilterFolderType(folderElement, fi); String msgHandleWhere = setWhereFilterMessageHandle(ap, fi); /* if message handle filter is available, the other filters should be ignored */ if (msgHandleWhere.isEmpty()) { where += setWhereFilterReadStatus(ap, fi); where += setWhereFilterPriority(ap, fi); where += setWhereFilterPeriod(ap, fi); if (fi.mMsgType == FilterInfo.TYPE_EMAIL) { where += setWhereFilterOriginatorEmail(ap); where += setWhereFilterRecipientEmail(ap); } if (fi.mMsgType == FilterInfo.TYPE_IM) { where += setWhereFilterOriginatorIM(ap); // TODO: set 'where' filer recipient? } where += setWhereFilterThreadId(ap, fi); } else { where += msgHandleWhere; } return where; } /* Used only for SMS/MMS */ private void setConvoWhereFilterSmsMms(StringBuilder selection, ArrayList selectionArgs, FilterInfo fi, BluetoothMapAppParams ap) { if (smsSelected(fi, ap) || mmsSelected(ap)) { // Filter Read Status if (ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_UNREAD_ONLY) != 0) { selection.append(" AND ").append(Threads.READ).append(" = 0"); } if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_READ_ONLY) != 0) { selection.append(" AND ").append(Threads.READ).append(" = 1"); } } // Filter time if ((ap.getFilterLastActivityBegin() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) { selection.append(" AND ") .append(Threads.DATE) .append(" >= ") .append(ap.getFilterLastActivityBegin()); } if ((ap.getFilterLastActivityEnd() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) { selection.append(" AND ") .append(Threads.DATE) .append(" <= ") .append(ap.getFilterLastActivityEnd()); } // Filter ConvoId long convoId = -1; if (ap.getFilterConvoId() != null) { convoId = ap.getFilterConvoId().getLeastSignificantBits(); } if (convoId > 0) { selection.append(" AND ") .append(Threads._ID) .append(" = ") .append(Long.toString(convoId)); } } } /** * Determine from application parameter if sms should be included. * The filter mask is set for message types not selected * @param fi * @param ap * @return boolean true if sms is selected, false if not */ private boolean smsSelected(FilterInfo fi, BluetoothMapAppParams ap) { int msgType = ap.getFilterMessageType(); int phoneType = fi.mPhoneType; if (D) { Log.d(TAG, "smsSelected msgType: " + msgType); } if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { return true; } if ((msgType & (BluetoothMapAppParams.FILTER_NO_SMS_CDMA | BluetoothMapAppParams.FILTER_NO_SMS_GSM)) == 0) { return true; } if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_GSM)) { return true; } if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_CDMA)) { return true; } return false; } /** * Determine from application parameter if mms should be included. * The filter mask is set for message types not selected * @param fi * @param ap * @return boolean true if mms is selected, false if not */ private boolean mmsSelected(BluetoothMapAppParams ap) { int msgType = ap.getFilterMessageType(); if (D) { Log.d(TAG, "mmsSelected msgType: " + msgType); } if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { return true; } if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0) { return true; } return false; } /** * Determine from application parameter if email should be included. * The filter mask is set for message types not selected * @param fi * @param ap * @return boolean true if email is selected, false if not */ private boolean emailSelected(BluetoothMapAppParams ap) { int msgType = ap.getFilterMessageType(); if (D) { Log.d(TAG, "emailSelected msgType: " + msgType); } if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { return true; } if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0) { return true; } return false; } /** * Determine from application parameter if IM should be included. * The filter mask is set for message types not selected * @param fi * @param ap * @return boolean true if im is selected, false if not */ private boolean imSelected(BluetoothMapAppParams ap) { int msgType = ap.getFilterMessageType(); if (D) { Log.d(TAG, "imSelected msgType: " + msgType); } if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { return true; } if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0) { return true; } return false; } private void setFilterInfo(FilterInfo fi) { TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); if (tm != null) { fi.mPhoneType = tm.getPhoneType(); fi.mPhoneNum = tm.getLine1Number(); } } /** * Get a listing of message in folder after applying filter. * @param folderElement Must contain a valid folder string != null * @param ap Parameters specifying message content and filters * @return Listing object containing requested messages */ public BluetoothMapMessageListing msgListing(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap) { if (D) { Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType()); } BluetoothMapMessageListing bmList = new BluetoothMapMessageListing(); /* We overwrite the parameter mask here if it is 0 or not present, as this * should cause all parameters to be included in the message list. */ if (ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER || ap.getParameterMask() == 0) { ap.setParameterMask(PARAMETER_MASK_ALL_ENABLED); if (V) { Log.v(TAG, "msgListing(): appParameterMask is zero or not present, " + "changing to all enabled by default: " + ap.getParameterMask()); } } if (V) { Log.v(TAG, "folderElement hasSmsMmsContent = " + folderElement.hasSmsMmsContent() + " folderElement.hasEmailContent = " + folderElement.hasEmailContent() + " folderElement.hasImContent = " + folderElement.hasImContent()); } /* Cache some info used throughout filtering */ FilterInfo fi = new FilterInfo(); setFilterInfo(fi); Cursor smsCursor = null; Cursor mmsCursor = null; Cursor emailCursor = null; Cursor imCursor = null; String limit = ""; int countNum = ap.getMaxListCount(); int offsetNum = ap.getStartOffset(); if (ap.getMaxListCount() > 0) { limit = " LIMIT " + (ap.getMaxListCount() + ap.getStartOffset()); } try { if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { if (ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL | BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_GSM | BluetoothMapAppParams.FILTER_NO_IM) || ap.getFilterMessageType() == ( BluetoothMapAppParams.FILTER_NO_EMAIL | BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA | BluetoothMapAppParams.FILTER_NO_IM)) { //set real limit and offset if only this type is used // (only if offset/limit is used) limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); if (D) { Log.d(TAG, "SMS Limit => " + limit); } offsetNum = 0; } fi.mMsgType = FilterInfo.TYPE_SMS; if (ap.getFilterPriority() != 1) { /*SMS cannot have high priority*/ String where = setWhereFilter(folderElement, fi, ap); if (D) { Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); } smsCursor = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit); if (smsCursor != null) { BluetoothMapMessageListingElement e = null; // store column index so we dont have to look them up anymore (optimization) if (D) { Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages."); } fi.setSmsColumns(smsCursor); while (smsCursor.moveToNext()) { if (matchAddresses(smsCursor, fi, ap)) { if (V) { BluetoothMapUtils.printCursor(smsCursor); } e = element(smsCursor, fi, ap); bmList.add(e); } } } } } if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { if (ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL | BluetoothMapAppParams.FILTER_NO_SMS_CDMA | BluetoothMapAppParams.FILTER_NO_SMS_GSM | BluetoothMapAppParams.FILTER_NO_IM)) { //set real limit and offset if only this type is used //(only if offset/limit is used) limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); if (D) { Log.d(TAG, "MMS Limit => " + limit); } offsetNum = 0; } fi.mMsgType = FilterInfo.TYPE_MMS; String where = setWhereFilter(folderElement, fi, ap); where += " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE; if (!where.isEmpty()) { if (D) { Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); } mmsCursor = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null, Mms.DATE + " DESC" + limit); if (mmsCursor != null) { BluetoothMapMessageListingElement e = null; // store column index so we dont have to look them up anymore (optimization) fi.setMmsColumns(mmsCursor); if (D) { Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages."); } while (mmsCursor.moveToNext()) { if (matchAddresses(mmsCursor, fi, ap)) { if (V) { BluetoothMapUtils.printCursor(mmsCursor); } e = element(mmsCursor, fi, ap); bmList.add(e); } } } } } if (emailSelected(ap) && folderElement.hasEmailContent()) { if (ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA | BluetoothMapAppParams.FILTER_NO_SMS_GSM | BluetoothMapAppParams.FILTER_NO_IM)) { //set real limit and offset if only this type is used //(only if offset/limit is used) limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); if (D) { Log.d(TAG, "Email Limit => " + limit); } offsetNum = 0; } fi.mMsgType = FilterInfo.TYPE_EMAIL; String where = setWhereFilter(folderElement, fi, ap); if (!where.isEmpty()) { if (D) { Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); } Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); emailCursor = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit); if (emailCursor != null) { BluetoothMapMessageListingElement e = null; // store column index so we dont have to look them up anymore (optimization) fi.setEmailMessageColumns(emailCursor); int cnt = 0; if (D) { Log.d(TAG, "Found " + emailCursor.getCount() + " email messages."); } while (emailCursor.moveToNext()) { if (V) { BluetoothMapUtils.printCursor(emailCursor); } e = element(emailCursor, fi, ap); bmList.add(e); } // emailCursor.close(); } } } if (imSelected(ap) && folderElement.hasImContent()) { if (ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA | BluetoothMapAppParams.FILTER_NO_SMS_GSM | BluetoothMapAppParams.FILTER_NO_EMAIL)) { //set real limit and offset if only this type is used //(only if offset/limit is used) limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); if (D) { Log.d(TAG, "IM Limit => " + limit); } offsetNum = 0; } fi.mMsgType = FilterInfo.TYPE_IM; String where = setWhereFilter(folderElement, fi, ap); if (D) { Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where); } Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); imCursor = mResolver.query(contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit); if (imCursor != null) { BluetoothMapMessageListingElement e = null; // store column index so we dont have to look them up anymore (optimization) fi.setImMessageColumns(imCursor); if (D) { Log.d(TAG, "Found " + imCursor.getCount() + " im messages."); } while (imCursor.moveToNext()) { if (V) { BluetoothMapUtils.printCursor(imCursor); } e = element(imCursor, fi, ap); bmList.add(e); } } } /* Enable this if post sorting and segmenting needed */ bmList.sort(); bmList.segment(ap.getMaxListCount(), offsetNum); List list = bmList.getList(); int listSize = list.size(); Cursor tmpCursor = null; for (int x = 0; x < listSize; x++) { BluetoothMapMessageListingElement ele = list.get(x); /* If OBEX "GET" request header includes "ParameterMask" with 'Type' NOT set, * then ele.getType() returns "null" even for a valid cursor. * Avoid NullPointerException in equals() check when 'mType' value is "null" */ TYPE tmpType = ele.getType(); if (smsCursor != null && ((TYPE.SMS_GSM).equals(tmpType) || (TYPE.SMS_CDMA).equals( tmpType))) { tmpCursor = smsCursor; fi.mMsgType = FilterInfo.TYPE_SMS; } else if (mmsCursor != null && (TYPE.MMS).equals(tmpType)) { tmpCursor = mmsCursor; fi.mMsgType = FilterInfo.TYPE_MMS; } else if (emailCursor != null && ((TYPE.EMAIL).equals(tmpType))) { tmpCursor = emailCursor; fi.mMsgType = FilterInfo.TYPE_EMAIL; } else if (imCursor != null && ((TYPE.IM).equals(tmpType))) { tmpCursor = imCursor; fi.mMsgType = FilterInfo.TYPE_IM; } if (tmpCursor != null) { tmpCursor.moveToPosition(ele.getCursorIndex()); setSenderAddressing(ele, tmpCursor, fi, ap); setSenderName(ele, tmpCursor, fi, ap); setRecipientAddressing(ele, tmpCursor, fi, ap); setRecipientName(ele, tmpCursor, fi, ap); setSubject(ele, tmpCursor, fi, ap); setSize(ele, tmpCursor, fi, ap); setText(ele, tmpCursor, fi, ap); setPriority(ele, tmpCursor, fi, ap); setSent(ele, tmpCursor, fi, ap); setProtected(ele, tmpCursor, fi, ap); setReceptionStatus(ele, tmpCursor, fi, ap); setAttachment(ele, tmpCursor, fi, ap); if (mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10) { setDeliveryStatus(ele, tmpCursor, fi, ap); setThreadId(ele, tmpCursor, fi, ap); setThreadName(ele, tmpCursor, fi, ap); } } } } finally { if (emailCursor != null) { emailCursor.close(); } if (smsCursor != null) { smsCursor.close(); } if (mmsCursor != null) { mmsCursor.close(); } if (imCursor != null) { imCursor.close(); } } if (D) { Log.d(TAG, "messagelisting end"); } return bmList; } /** * Get the size of the message listing * @param folderElement Must contain a valid folder string != null * @param ap Parameters specifying message content and filters * @return Integer equal to message listing size */ public int msgListingSize(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap) { if (D) { Log.d(TAG, "msgListingSize: folder = " + folderElement.getName()); } int cnt = 0; /* Cache some info used throughout filtering */ FilterInfo fi = new FilterInfo(); setFilterInfo(fi); if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { fi.mMsgType = FilterInfo.TYPE_SMS; String where = setWhereFilter(folderElement, fi, ap); Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC"); try { if (c != null) { cnt = c.getCount(); } } finally { if (c != null) { c.close(); } } } if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { fi.mMsgType = FilterInfo.TYPE_MMS; String where = setWhereFilter(folderElement, fi, ap); Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null, Mms.DATE + " DESC"); try { if (c != null) { cnt += c.getCount(); } } finally { if (c != null) { c.close(); } } } if (emailSelected(ap) && folderElement.hasEmailContent()) { fi.mMsgType = FilterInfo.TYPE_EMAIL; String where = setWhereFilter(folderElement, fi, ap); if (!where.isEmpty()) { Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null, BluetoothMapContract.MessageColumns.DATE + " DESC"); try { if (c != null) { cnt += c.getCount(); } } finally { if (c != null) { c.close(); } } } } if (imSelected(ap) && folderElement.hasImContent()) { fi.mMsgType = FilterInfo.TYPE_IM; String where = setWhereFilter(folderElement, fi, ap); if (!where.isEmpty()) { Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null, BluetoothMapContract.MessageColumns.DATE + " DESC"); try { if (c != null) { cnt += c.getCount(); } } finally { if (c != null) { c.close(); } } } } if (D) { Log.d(TAG, "msgListingSize: size = " + cnt); } return cnt; } /** * Return true if there are unread messages in the requested list of messages * @param folderElement folder where the message listing should come from * @param ap application parameter object * @return true if unread messages are in the list, else false */ public boolean msgListingHasUnread(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap) { if (D) { Log.d(TAG, "msgListingHasUnread: folder = " + folderElement.getName()); } int cnt = 0; /* Cache some info used throughout filtering */ FilterInfo fi = new FilterInfo(); setFilterInfo(fi); if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) { fi.mMsgType = FilterInfo.TYPE_SMS; String where = setWhereFilterFolderType(folderElement, fi); where += " AND " + Sms.READ + "=0 "; where += setWhereFilterPeriod(ap, fi); Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC"); try { if (c != null) { cnt = c.getCount(); } } finally { if (c != null) { c.close(); } } } if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) { fi.mMsgType = FilterInfo.TYPE_MMS; String where = setWhereFilterFolderType(folderElement, fi); where += " AND " + Mms.READ + "=0 "; where += setWhereFilterPeriod(ap, fi); Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null, Sms.DATE + " DESC"); try { if (c != null) { cnt += c.getCount(); } } finally { if (c != null) { c.close(); } } } if (emailSelected(ap) && folderElement.getFolderId() != -1) { fi.mMsgType = FilterInfo.TYPE_EMAIL; String where = setWhereFilterFolderType(folderElement, fi); if (!where.isEmpty()) { where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 "; where += setWhereFilterPeriod(ap, fi); Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null, BluetoothMapContract.MessageColumns.DATE + " DESC"); try { if (c != null) { cnt += c.getCount(); } } finally { if (c != null) { c.close(); } } } } if (imSelected(ap) && folderElement.hasImContent()) { fi.mMsgType = FilterInfo.TYPE_IM; String where = setWhereFilter(folderElement, fi, ap); if (!where.isEmpty()) { where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 "; where += setWhereFilterPeriod(ap, fi); Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null, BluetoothMapContract.MessageColumns.DATE + " DESC"); try { if (c != null) { cnt += c.getCount(); } } finally { if (c != null) { c.close(); } } } } if (D) { Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt); } return cnt > 0; } /** * Build the conversation listing. * @param ap The Application Parameters * @param sizeOnly TRUE: don't populate the list members, only build the list to get the size. * @return */ public BluetoothMapConvoListing convoListing(BluetoothMapAppParams ap, boolean sizeOnly) { if (D) { Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType()); } BluetoothMapConvoListing convoList = new BluetoothMapConvoListing(); /* We overwrite the parameter mask here if it is 0 or not present, as this * should cause all parameters to be included in the message list. */ if (ap.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER || ap.getConvoParameterMask() == 0) { ap.setConvoParameterMask(CONVO_PARAMETER_MASK_DEFAULT); if (D) { Log.v(TAG, "convoListing(): appParameterMask is zero or not present, " + "changing to default: " + ap.getConvoParameterMask()); } } /* Possible filters: * - Recipient name (contacts DB) or id (for SMS/MMS this is the thread-id contact-id) * - Activity start/begin * - Read status * - Thread_id * The strategy for SMS/MMS * With no filter on name - use limit and offset. * With a filter on name - build the complete list of conversations and create a filter * mechanism * * The strategy for IM: * Join the conversation table with the contacts table in a way that makes it possible to * get the data needed in a single query. * Manually handle limit/offset * */ /* Cache some info used throughout filtering */ FilterInfo fi = new FilterInfo(); setFilterInfo(fi); Cursor smsMmsCursor = null; Cursor imEmailCursor = null; int offsetNum; if (sizeOnly) { offsetNum = 0; } else { offsetNum = ap.getStartOffset(); } // Inverse meaning - hence a 1 is include. int msgTypesInclude = ((~ap.getFilterMessageType()) & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK); int maxThreads = ap.getMaxListCount() + ap.getStartOffset(); try { if (smsSelected(fi, ap) || mmsSelected(ap)) { String limit = ""; if ((!sizeOnly) && (ap.getMaxListCount() > 0) && (ap.getFilterRecipient() == null)) { /* We can only use limit if we do not have a contacts filter */ limit = " LIMIT " + maxThreads; } StringBuilder sortOrder = new StringBuilder(Threads.DATE + " DESC"); if ((!sizeOnly) && ((msgTypesInclude & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM | BluetoothMapAppParams.FILTER_NO_SMS_CDMA) | BluetoothMapAppParams.FILTER_NO_MMS) == 0) && ap.getFilterRecipient() == null) { // SMS/MMS messages only and no recipient filter - use optimization. limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset(); if (D) { Log.d(TAG, "SMS Limit => " + limit); } offsetNum = 0; } StringBuilder selection = new StringBuilder(120); // This covers most cases ArrayList selectionArgs = new ArrayList(12); // Covers all cases selection.append("1=1 "); // just to simplify building the where-clause setConvoWhereFilterSmsMms(selection, selectionArgs, fi, ap); String[] args = null; if (selectionArgs.size() > 0) { args = new String[selectionArgs.size()]; selectionArgs.toArray(args); } Uri uri = Threads.CONTENT_URI.buildUpon() .appendQueryParameter("simple", "true") .build(); sortOrder.append(limit); if (D) { Log.d(TAG, "Query using selection: " + selection.toString() + " - sortOrder: " + sortOrder.toString()); } // TODO: Optimize: Reduce projection based on convo parameter mask smsMmsCursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(), args, sortOrder.toString()); if (smsMmsCursor != null) { // store column index so we don't have to look them up anymore (optimization) if (D) { Log.d(TAG, "Found " + smsMmsCursor.getCount() + " sms/mms conversations."); } BluetoothMapConvoListingElement convoElement = null; smsMmsCursor.moveToPosition(-1); if (ap.getFilterRecipient() == null) { int count = 0; // We have no Recipient filter, add contacts after the list is reduced while (smsMmsCursor.moveToNext()) { convoElement = createConvoElement(smsMmsCursor, fi, ap); convoList.add(convoElement); count++; if (!sizeOnly && count >= maxThreads) { break; } } } else { // We must be able to filter on recipient, add contacts now SmsMmsContacts contacts = new SmsMmsContacts(); while (smsMmsCursor.moveToNext()) { int count = 0; convoElement = createConvoElement(smsMmsCursor, fi, ap); String idsStr = smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); // Add elements only if we do find a contact - if not we cannot apply // the filter, hence the item is irrelevant // TODO: Perhaps the spec. should be changes to be able to search on // phone number as well? if (addSmsMmsContacts(convoElement, contacts, idsStr, ap.getFilterRecipient(), ap)) { convoList.add(convoElement); if (!sizeOnly && count >= maxThreads) { break; } } } } } } if (emailSelected(ap) || imSelected(ap)) { int count = 0; if (emailSelected(ap)) { fi.mMsgType = FilterInfo.TYPE_EMAIL; } else if (imSelected(ap)) { fi.mMsgType = FilterInfo.TYPE_IM; } if (D) { Log.d(TAG, "msgType: " + fi.mMsgType); } Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION); contentUri = appendConvoListQueryParameters(ap, contentUri); if (V) { Log.v(TAG, "URI with parameters: " + contentUri.toString()); } // TODO: Optimize: Reduce projection based on convo parameter mask imEmailCursor = mResolver.query(contentUri, BluetoothMapContract.BT_CONVERSATION_PROJECTION, null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID + " ASC"); if (imEmailCursor != null) { BluetoothMapConvoListingElement e = null; // store column index so we don't have to look them up anymore (optimization) // Here we rely on only a single account-based message type for each MAS. fi.setEmailImConvoColumns(imEmailCursor); boolean isValid = imEmailCursor.moveToNext(); if (D) { Log.d(TAG, "Found " + imEmailCursor.getCount() + " EMAIL/IM conversations. isValid = " + isValid); } while (isValid && ((sizeOnly) || (count < maxThreads))) { long threadId = imEmailCursor.getLong(fi.mConvoColConvoId); long nextThreadId; count++; e = createConvoElement(imEmailCursor, fi, ap); convoList.add(e); do { nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId); if (V) { Log.i(TAG, " threadId = " + threadId + " newThreadId = " + nextThreadId); } // TODO: This seems rather inefficient in the case where we do not need // to reduce the list. } while ((nextThreadId == threadId) && (isValid = imEmailCursor.moveToNext())); } } } if (D) { Log.d(TAG, "Done adding conversations - list size:" + convoList.getCount()); } // If sizeOnly - we are all done here - return the list as is - no need to populate the // list. if (sizeOnly) { return convoList; } /* Enable this if post sorting and segmenting needed */ /* This is too early */ convoList.sort(); convoList.segment(ap.getMaxListCount(), offsetNum); List list = convoList.getList(); int listSize = list.size(); if (V) { Log.i(TAG, "List Size:" + listSize); } Cursor tmpCursor = null; SmsMmsContacts contacts = new SmsMmsContacts(); for (int x = 0; x < listSize; x++) { BluetoothMapConvoListingElement ele = list.get(x); TYPE type = ele.getType(); switch (type) { case SMS_CDMA: case SMS_GSM: case MMS: { tmpCursor = null; // SMS/MMS needs special treatment if (smsMmsCursor != null) { populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts); } if (D) { fi.mMsgType = FilterInfo.TYPE_IM; } break; } case EMAIL: tmpCursor = imEmailCursor; fi.mMsgType = FilterInfo.TYPE_EMAIL; break; case IM: tmpCursor = imEmailCursor; fi.mMsgType = FilterInfo.TYPE_IM; break; default: tmpCursor = null; break; } if (D) { Log.d(TAG, "Working on cursor of type " + fi.mMsgType); } if (tmpCursor != null) { populateImEmailConvoElement(ele, tmpCursor, ap, fi); } else { // No, it will be for SMS/MMS at the moment if (D) { Log.d(TAG, "tmpCursor is Null - something is wrong - or the message is" + " of type SMS/MMS"); } } } } finally { if (imEmailCursor != null) { imEmailCursor.close(); } if (smsMmsCursor != null) { smsMmsCursor.close(); } if (D) { Log.d(TAG, "conversation end"); } } return convoList; } /** * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a * new ConvoListVersinoCounter in mSmsMmsConvoListVersion * @return */ /* package */ boolean refreshSmsMmsConvoVersions() { boolean listChangeDetected = false; Cursor cursor = null; Uri uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); cursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null, null, Threads.DATE + " DESC"); try { if (cursor != null) { // store column index so we don't have to look them up anymore (optimization) if (D) { Log.d(TAG, "Found " + cursor.getCount() + " sms/mms conversations."); } BluetoothMapConvoListingElement convoElement = null; cursor.moveToPosition(-1); synchronized (getSmsMmsConvoList()) { int size = Math.max(getSmsMmsConvoList().size(), cursor.getCount()); HashMap newList = new HashMap(size); while (cursor.moveToNext()) { // TODO: Extract to function, that can be called at listing, which returns // the versionCounter(existing or new). boolean convoChanged = false; Long id = cursor.getLong(MMS_SMS_THREAD_COL_ID); convoElement = getSmsMmsConvoList().remove(id); if (convoElement == null) { // New conversation added convoElement = new BluetoothMapConvoListingElement(); convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id); listChangeDetected = true; convoElement.setVersionCounter(0); } // Currently we only need to compare name, lastActivity and read_status, and // name is not used for SMS/MMS. // msg delete will be handled by update folderVersionCounter(). long lastActivity = cursor.getLong(MMS_SMS_THREAD_COL_DATE); boolean read = cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1; if (lastActivity != convoElement.getLastActivity()) { convoChanged = true; convoElement.setLastActivity(lastActivity); } if (read != convoElement.getReadBool()) { convoChanged = true; convoElement.setRead(read, false); } String idsStr = cursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); if (!idsStr.equals(convoElement.getSmsMmsContacts())) { // This should not trigger a change in conversationVersionCounter // only the // ConvoListVersionCounter. listChangeDetected = true; convoElement.setSmsMmsContacts(idsStr); } if (convoChanged) { listChangeDetected = true; convoElement.incrementVersionCounter(); } newList.put(id, convoElement); } // If we still have items on the old list, something was deleted if (getSmsMmsConvoList().size() != 0) { listChangeDetected = true; } setSmsMmsConvoList(newList); } if (listChangeDetected) { mMasInstance.updateSmsMmsConvoListVersionCounter(); } } } finally { if (cursor != null) { cursor.close(); } } return listChangeDetected; } /** * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a * new ConvoListVersinoCounter in mSmsMmsConvoListVersion * @return */ /* package */ boolean refreshImEmailConvoVersions() { boolean listChangeDetected = false; FilterInfo fi = new FilterInfo(); Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION); if (V) { Log.v(TAG, "URI with parameters: " + contentUri.toString()); } Cursor imEmailCursor = mResolver.query(contentUri, CONVO_VERSION_PROJECTION, null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID + " ASC"); try { if (imEmailCursor != null) { BluetoothMapConvoListingElement convoElement = null; // store column index so we don't have to look them up anymore (optimization) // Here we rely on only a single account-based message type for each MAS. fi.setEmailImConvoColumns(imEmailCursor); boolean isValid = imEmailCursor.moveToNext(); if (V) { Log.d(TAG, "Found " + imEmailCursor.getCount() + " EMAIL/IM conversations. isValid = " + isValid); } synchronized (getImEmailConvoList()) { int size = Math.max(getImEmailConvoList().size(), imEmailCursor.getCount()); boolean convoChanged = false; HashMap newList = new HashMap(size); while (isValid) { long id = imEmailCursor.getLong(fi.mConvoColConvoId); long nextThreadId; convoElement = getImEmailConvoList().remove(id); if (convoElement == null) { // New conversation added convoElement = new BluetoothMapConvoListingElement(); convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id); listChangeDetected = true; convoElement.setVersionCounter(0); } String name = imEmailCursor.getString(fi.mConvoColName); String summary = imEmailCursor.getString(fi.mConvoColSummary); long lastActivity = imEmailCursor.getLong(fi.mConvoColLastActivity); boolean read = imEmailCursor.getInt(fi.mConvoColRead) == 1; if (lastActivity != convoElement.getLastActivity()) { convoChanged = true; convoElement.setLastActivity(lastActivity); } if (read != convoElement.getReadBool()) { convoChanged = true; convoElement.setRead(read, false); } if (name != null && !name.equals(convoElement.getName())) { convoChanged = true; convoElement.setName(name); } if (summary != null && !summary.equals(convoElement.getFullSummary())) { convoChanged = true; convoElement.setSummary(summary); } /* If the query returned one row for each contact, skip all the dublicates */ do { nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId); if (V) { Log.i(TAG, " threadId = " + id + " newThreadId = " + nextThreadId); } } while ((nextThreadId == id) && (isValid = imEmailCursor.moveToNext())); if (convoChanged) { listChangeDetected = true; convoElement.incrementVersionCounter(); } newList.put(id, convoElement); } // If we still have items on the old list, something was deleted if (getImEmailConvoList().size() != 0) { listChangeDetected = true; } setImEmailConvoList(newList); } } } finally { if (imEmailCursor != null) { imEmailCursor.close(); } } if (listChangeDetected) { mMasInstance.updateImEmailConvoListVersionCounter(); } return listChangeDetected; } /** * Update the convoVersionCounter within the element passed as parameter. * This function has the side effect to update the ConvoListVersionCounter if needed. * This function ignores changes to contacts as this shall not change the convoVersionCounter, * only the convoListVersion counter, which will be updated upon request. * @param ele Element to update shall not be null. */ private void updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele) { long id = ele.getCpConvoId(); BluetoothMapConvoListingElement convoElement = getSmsMmsConvoList().get(id); boolean listChangeDetected = false; boolean convoChanged = false; if (convoElement == null) { // New conversation added convoElement = new BluetoothMapConvoListingElement(); getSmsMmsConvoList().put(id, convoElement); convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id); listChangeDetected = true; convoElement.setVersionCounter(0); } long lastActivity = cursor.getLong(MMS_SMS_THREAD_COL_DATE); boolean read = cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1; if (lastActivity != convoElement.getLastActivity()) { convoChanged = true; convoElement.setLastActivity(lastActivity); } if (read != convoElement.getReadBool()) { convoChanged = true; convoElement.setRead(read, false); } if (convoChanged) { listChangeDetected = true; convoElement.incrementVersionCounter(); } if (listChangeDetected) { mMasInstance.updateSmsMmsConvoListVersionCounter(); } ele.setVersionCounter(convoElement.getVersionCounter()); } /** * Update the convoVersionCounter within the element passed as parameter. * This function has the side effect to update the ConvoListVersionCounter if needed. * This function ignores changes to contacts as this shall not change the convoVersionCounter, * only the convoListVersion counter, which will be updated upon request. * @param ele Element to update shall not be null. */ private void updateImEmailConvoVersion(Cursor cursor, FilterInfo fi, BluetoothMapConvoListingElement ele) { long id = ele.getCpConvoId(); BluetoothMapConvoListingElement convoElement = getImEmailConvoList().get(id); boolean listChangeDetected = false; boolean convoChanged = false; if (convoElement == null) { // New conversation added if (V) { Log.d(TAG, "Added new conversation with ID = " + id); } convoElement = new BluetoothMapConvoListingElement(); convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id); getImEmailConvoList().put(id, convoElement); listChangeDetected = true; convoElement.setVersionCounter(0); } String name = cursor.getString(fi.mConvoColName); long lastActivity = cursor.getLong(fi.mConvoColLastActivity); boolean read = cursor.getInt(fi.mConvoColRead) == 1; if (lastActivity != convoElement.getLastActivity()) { convoChanged = true; convoElement.setLastActivity(lastActivity); } if (read != convoElement.getReadBool()) { convoChanged = true; convoElement.setRead(read, false); } if (name != null && !name.equals(convoElement.getName())) { convoChanged = true; convoElement.setName(name); } if (convoChanged) { listChangeDetected = true; if (V) { Log.d(TAG, "conversation with ID = " + id + " changed"); } convoElement.incrementVersionCounter(); } if (listChangeDetected) { mMasInstance.updateImEmailConvoListVersionCounter(); } ele.setVersionCounter(convoElement.getVersionCounter()); } /** * @param ele * @param smsMmsCursor * @param ap * @param contacts */ private void populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele, Cursor smsMmsCursor, BluetoothMapAppParams ap, SmsMmsContacts contacts) { smsMmsCursor.moveToPosition(ele.getCursorIndex()); // TODO: If we ever get beyond 31 bit, change to long int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value // TODO: How to determine whether the convo-IDs can be used across message // types? ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, smsMmsCursor.getLong(MMS_SMS_THREAD_COL_ID)); boolean read = smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1; if ((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) { ele.setRead(read, true); } else { ele.setRead(read, false); } if ((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) { long timeStamp = smsMmsCursor.getLong(MMS_SMS_THREAD_COL_DATE); ele.setLastActivity(timeStamp); } else { // We need to delete the time stamp, if it was added for multi msg-type ele.setLastActivity(-1); } if ((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) { updateSmsMmsConvoVersion(smsMmsCursor, ele); } if ((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) { ele.setName(""); // We never have a thread name for SMS/MMS } if ((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) { String summary = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET); String cs = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET_CS); if (summary != null && cs != null && !cs.equals("UTF-8")) { try { // TODO: Not sure this is how to convert to UTF-8 summary = new String(summary.getBytes(cs), "UTF-8"); } catch (UnsupportedEncodingException e) { Log.e(TAG, "populateSmsMmsConvoElement: " + e); } } ele.setSummary(summary); } if ((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) { if (ap.getFilterRecipient() == null) { // Add contacts only if not already added String idsStr = smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS); addSmsMmsContacts(ele, contacts, idsStr, null, ap); } } } /** * @param ele * @param tmpCursor * @param fi */ private void populateImEmailConvoElement(BluetoothMapConvoListingElement ele, Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi) { tmpCursor.moveToPosition(ele.getCursorIndex()); // TODO: If we ever get beyond 31 bit, change to long int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value long threadId = tmpCursor.getLong(fi.mConvoColConvoId); // Mandatory field ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, threadId); if ((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) { ele.setName(tmpCursor.getString(fi.mConvoColName)); } boolean reportRead = false; if ((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) { reportRead = true; } ele.setRead((1 == tmpCursor.getInt(fi.mConvoColRead)), reportRead); long timestamp = tmpCursor.getLong(fi.mConvoColLastActivity); if ((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) { ele.setLastActivity(timestamp); } else { // We need to delete the time stamp, if it was added for multi msg-type ele.setLastActivity(-1); } if ((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) { updateImEmailConvoVersion(tmpCursor, fi, ele); } if ((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) { ele.setSummary(tmpCursor.getString(fi.mConvoColSummary)); } // TODO: For optimization, we could avoid joining the contact and convo tables // if we have no filter nor this bit is set. if ((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) { do { BluetoothMapConvoContactElement c = new BluetoothMapConvoContactElement(); if ((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) { c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid), 0)); } if ((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) { c.setChatState(tmpCursor.getInt(fi.mContactColChatState)); } if ((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) { c.setPresenceAvailability(tmpCursor.getInt(fi.mContactColPresenceState)); } if ((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) { c.setPresenceStatus(tmpCursor.getString(fi.mContactColPresenceText)); } if ((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) { c.setPriority(tmpCursor.getInt(fi.mContactColPriority)); } if ((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) { c.setDisplayName(tmpCursor.getString(fi.mContactColNickname)); } if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) { c.setContactId(tmpCursor.getString(fi.mContactColContactUci)); } if ((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) { c.setLastActivity(tmpCursor.getLong(fi.mContactColLastActive)); } if ((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) { c.setName(tmpCursor.getString(fi.mContactColName)); } ele.addContact(c); } while (tmpCursor.moveToNext() && tmpCursor.getLong(fi.mConvoColConvoId) == threadId); } } /** * Extract the ConvoList parameters from appParams and build the matching URI with * query parameters. * @param ap the appParams from the request * @param contentUri the URI to append parameters to * @return the new URI with the appended parameters (if any) */ private Uri appendConvoListQueryParameters(BluetoothMapAppParams ap, Uri contentUri) { Builder newUri = contentUri.buildUpon(); String str = ap.getFilterRecipient(); if (str != null) { str = str.trim(); str = str.replace("*", "%"); newUri.appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, str); } long time = ap.getFilterLastActivityBegin(); if (time > 0) { newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN, Long.toString(time)); } time = ap.getFilterLastActivityEnd(); if (time > 0) { newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_END, Long.toString(time)); } int readStatus = ap.getFilterReadStatus(); if (readStatus > 0) { if (readStatus == 1) { // Conversations with Unread messages only newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS, "false"); } else if (readStatus == 2) { // Conversations with all read messages only newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS, "true"); } // if both are set it will be the same as requesting an empty list, but // as it makes no sense with such a structure in a bit mask, we treat // requesting both the same as no filtering. } long convoId = -1; if (ap.getFilterConvoId() != null) { convoId = ap.getFilterConvoId().getLeastSignificantBits(); } if (convoId > 0) { newUri.appendQueryParameter(BluetoothMapContract.FILTER_THREAD_ID, Long.toString(convoId)); } return newUri.build(); } /** * Procedure if we have a filter: * - loop through all ids to examine if there is a match (this will build the cache) * - If there is a match loop again to add all contacts. * * Procedure if we don't have a filter * - Add all contacts * * @param convoElement * @param contacts * @param idsStr * @param recipientFilter * @return */ private boolean addSmsMmsContacts(BluetoothMapConvoListingElement convoElement, SmsMmsContacts contacts, String idsStr, String recipientFilter, BluetoothMapAppParams ap) { BluetoothMapConvoContactElement contactElement; int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value boolean foundContact = false; String[] ids = idsStr.split(" "); long[] longIds = new long[ids.length]; if (recipientFilter != null) { recipientFilter = recipientFilter.trim(); } for (int i = 0; i < ids.length; i++) { long longId; try { longId = Long.parseLong(ids[i]); longIds[i] = longId; if (recipientFilter == null) { // If there is not filter, all we need to do is to parse the ids foundContact = true; continue; } String addr = contacts.getPhoneNumber(mResolver, longId); if (addr == null) { // This can only happen if all messages from a contact is deleted while // performing the query. continue; } MapContact contact = contacts.getContactNameFromPhone(addr, mResolver, recipientFilter); if (D) { Log.d(TAG, " id " + longId + ": " + addr); if (contact != null) { Log.d(TAG, " contact name: " + contact.getName() + " X-BT-UID: " + contact .getXBtUid()); } } if (contact == null) { continue; } foundContact = true; } catch (NumberFormatException ex) { // skip this id continue; } } if (foundContact) { foundContact = false; for (long id : longIds) { String addr = contacts.getPhoneNumber(mResolver, id); if (addr == null) { // This can only happen if all messages from a contact is deleted while // performing the query. continue; } foundContact = true; MapContact contact = contacts.getContactNameFromPhone(addr, mResolver); if (contact == null) { // We do not have a contact, we need to manually add one contactElement = new BluetoothMapConvoContactElement(); if ((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) { contactElement.setName(addr); // Use the phone number as name } if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) { contactElement.setContactId(addr); } } else { contactElement = BluetoothMapConvoContactElement.createFromMapContact(contact, addr); // Remove the parameters not to be reported if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) { contactElement.setContactId(null); } if ((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) { contactElement.setBtUid(null); } if ((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) { contactElement.setDisplayName(null); } } convoElement.addContact(contactElement); } } return foundContact; } /** * Get the folder name of an SMS message or MMS message. * @param c the cursor pointing at the message * @return the folder name. */ private String getFolderName(int type, int threadId) { if (threadId == -1) { return BluetoothMapContract.FOLDER_NAME_DELETED; } switch (type) { case 1: return BluetoothMapContract.FOLDER_NAME_INBOX; case 2: return BluetoothMapContract.FOLDER_NAME_SENT; case 3: return BluetoothMapContract.FOLDER_NAME_DRAFT; case 4: // Just name outbox, failed and queued "outbox" case 5: case 6: return BluetoothMapContract.FOLDER_NAME_OUTBOX; } return ""; } public byte[] getMessage(String handle, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement, String version) throws UnsupportedEncodingException { TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle); mMessageVersion = version; long id = BluetoothMapUtils.getCpHandle(handle); if (appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) { throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as" + " we always return the full message."); } switch (type) { case SMS_GSM: case SMS_CDMA: return getSmsMessage(id, appParams.getCharset()); case MMS: return getMmsMessage(id, appParams); case EMAIL: return getEmailMessage(id, appParams, folderElement); case IM: return getIMMessage(id, appParams, folderElement); } throw new IllegalArgumentException("Invalid message handle."); } private String setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming) { String contactId = null, contactName = null; String[] phoneNumbers = new String[1]; //Handle possible exception for empty phone address if (TextUtils.isEmpty(phone)) { return contactName; } // // Use only actual phone number, because the MCE cannot know which // number the message is from. // phoneNumbers[0] = phone; String[] emailAddresses = null; Cursor p; Uri uri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone)); String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME}; String selection = Contacts.IN_VISIBLE_GROUP + "=1"; String orderBy = Contacts._ID + " ASC"; // Get the contact _ID and name p = mResolver.query(uri, projection, selection, null, orderBy); try { if (p != null && p.moveToFirst()) { contactId = p.getString(p.getColumnIndex(Contacts._ID)); contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME)); } } finally { close(p); } // Bail out if we are unable to find a contact, based on the phone number if (contactId != null) { Cursor q = null; // Fetch the contact e-mail addresses try { q = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[]{contactId}, null); if (q != null && q.moveToFirst()) { int i = 0; emailAddresses = new String[q.getCount()]; do { String emailAddress = q.getString( q.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS)); emailAddresses[i++] = emailAddress; } while (q != null && q.moveToNext()); } } finally { close(q); } } if (incoming) { if (V) { Log.d(TAG, "Adding originator for phone:" + phone); } // Use version 3.0 as we only have a formatted name message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses, null, null); } else { if (V) { Log.d(TAG, "Adding recipient for phone:" + phone); } // Use version 3.0 as we only have a formatted name message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses, null, null); } return contactName; } public static final int MAP_MESSAGE_CHARSET_NATIVE = 0; public static final int MAP_MESSAGE_CHARSET_UTF8 = 1; public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException { int type, threadId; long time = -1; String msgBody; BluetoothMapbMessageSms message = new BluetoothMapbMessageSms(); TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null); if (c == null || !c.moveToFirst()) { throw new IllegalArgumentException("SMS handle not found"); } try { if (c != null && c.moveToFirst()) { if (V) { Log.v(TAG, "c.count: " + c.getCount()); } if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { message.setType(TYPE.SMS_CDMA); } else { // set SMS_GSM by default message.setType(TYPE.SMS_GSM); } message.setVersionString(mMessageVersion); String read = c.getString(c.getColumnIndex(Sms.READ)); if (read.equalsIgnoreCase("1")) { message.setStatus(true); } else { message.setStatus(false); } type = c.getInt(c.getColumnIndex(Sms.TYPE)); threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID)); message.setFolder(getFolderName(type, threadId)); msgBody = c.getString(c.getColumnIndex(Sms.BODY)); String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); if ((phone == null) && type == Sms.MESSAGE_TYPE_DRAFT) { //Fetch address for Drafts folder from "canonical_address" table phone = getCanonicalAddressSms(mResolver, threadId); } time = c.getLong(c.getColumnIndex(Sms.DATE)); if (type == 1) { // Inbox message needs to set the vCard as originator setVCardFromPhoneNumber(message, phone, true); } else { // Other messages sets the vCard as the recipient setVCardFromPhoneNumber(message, phone, false); } if (charset == MAP_MESSAGE_CHARSET_NATIVE) { if (type == 1) { //Inbox message.setSmsBodyPdus( BluetoothMapSmsPdu.getDeliverPdus(mContext, msgBody, phone, time)); } else { message.setSmsBodyPdus( BluetoothMapSmsPdu.getSubmitPdus(mContext, msgBody, phone)); } } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ { message.setSmsBody(msgBody); } return message.encode(); } } finally { if (c != null) { c.close(); } } return message.encode(); } private void extractMmsAddresses(long id, BluetoothMapbMessageMime message) { final String[] projection = null; String selection = new String(Mms.Addr.MSG_ID + "=" + id); String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr"); Uri uriAddress = Uri.parse(uriStr); String contactName = null; Cursor c = mResolver.query(uriAddress, projection, selection, null, null); try { if (c.moveToFirst()) { do { String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS)); if (address.equals(INSERT_ADDRES_TOKEN)) { continue; } Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE)); switch (type) { case MMS_FROM: contactName = setVCardFromPhoneNumber(message, address, true); message.addFrom(contactName, address); break; case MMS_TO: contactName = setVCardFromPhoneNumber(message, address, false); message.addTo(contactName, address); break; case MMS_CC: contactName = setVCardFromPhoneNumber(message, address, false); message.addCc(contactName, address); break; case MMS_BCC: contactName = setVCardFromPhoneNumber(message, address, false); message.addBcc(contactName, address); break; default: break; } } while (c.moveToNext()); } } finally { if (c != null) { c.close(); } } } /** * Read out a mime data part and return the data in a byte array. * @param contentPartUri TODO * @param partid the content provider id of the Mime Part. * @return */ private byte[] readRawDataPart(Uri contentPartUri, long partid) { String uriStr = new String(contentPartUri + "/" + partid); Uri uriAddress = Uri.parse(uriStr); InputStream is = null; ByteArrayOutputStream os = new ByteArrayOutputStream(); int bufferSize = 8192; byte[] buffer = new byte[bufferSize]; byte[] retVal = null; try { is = mResolver.openInputStream(uriAddress); int len = 0; while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize } retVal = os.toByteArray(); } catch (IOException e) { // do nothing for now Log.w(TAG, "Error reading part data", e); } finally { close(os); close(is); } return retVal; } /** * Read out the mms parts and update the bMessage object provided i {@linkplain message} * @param id the content provider ID of the message * @param message the bMessage object to add the information to */ private void extractMmsParts(long id, BluetoothMapbMessageMime message) { /* Handling of filtering out non-text parts for exclude * attachments is handled within the bMessage object. */ final String[] projection = null; String selection = new String(Mms.Part.MSG_ID + "=" + id); String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part"); Uri uriAddress = Uri.parse(uriStr); BluetoothMapbMessageMime.MimePart part; Cursor c = mResolver.query(uriAddress, projection, selection, null, null); try { if (c.moveToFirst()) { do { Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID)); String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE)); String name = c.getString(c.getColumnIndex(Mms.Part.NAME)); String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET)); String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME)); String text = c.getString(c.getColumnIndex(Mms.Part.TEXT)); Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA)); String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID)); String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION)); String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION)); if (V) { Log.d(TAG, " _id : " + partId + "\n ct : " + contentType + "\n partname : " + name + "\n charset : " + charset + "\n filename : " + filename + "\n text : " + text + "\n fd : " + fd + "\n cid : " + cid + "\n cl : " + cl + "\n cdisp : " + cdisp); } part = message.addMimePart(); part.mContentType = contentType; part.mPartName = name; part.mContentId = cid; part.mContentLocation = cl; part.mContentDisposition = cdisp; try { if (text != null) { part.mData = text.getBytes("UTF-8"); part.mCharsetName = "utf-8"; } else { part.mData = readRawDataPart(Uri.parse(Mms.CONTENT_URI + "/part"), partId); if (charset != null) { part.mCharsetName = CharacterSets.getMimeName(Integer.parseInt(charset)); } } } catch (NumberFormatException e) { Log.d(TAG, "extractMmsParts", e); part.mData = null; part.mCharsetName = null; } catch (UnsupportedEncodingException e) { Log.d(TAG, "extractMmsParts", e); part.mData = null; part.mCharsetName = null; } part.mFileName = filename; } while (c.moveToNext()); message.updateCharset(); } } finally { if (c != null) { c.close(); } } } /** * Read out the mms parts and update the bMessage object provided i {@linkplain message} * @param id the content provider ID of the message * @param message the bMessage object to add the information to */ private void extractIMParts(long id, BluetoothMapbMessageMime message) { /* Handling of filtering out non-text parts for exclude * attachments is handled within the bMessage object. */ final String[] projection = null; String selection = new String(BluetoothMapContract.MessageColumns._ID + "=" + id); String uriStr = new String(mBaseUri + BluetoothMapContract.TABLE_MESSAGE + "/" + id + "/part"); Uri uriAddress = Uri.parse(uriStr); BluetoothMapbMessageMime.MimePart part; Cursor c = mResolver.query(uriAddress, projection, selection, null, null); try { if (c.moveToFirst()) { do { Long partId = c.getLong( c.getColumnIndex(BluetoothMapContract.MessagePartColumns._ID)); String charset = c.getString( c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CHARSET)); String filename = c.getString( c.getColumnIndex(BluetoothMapContract.MessagePartColumns.FILENAME)); String text = c.getString( c.getColumnIndex(BluetoothMapContract.MessagePartColumns.TEXT)); String body = c.getString( c.getColumnIndex(BluetoothMapContract.MessagePartColumns.RAW_DATA)); String cid = c.getString( c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CONTENT_ID)); if (V) { Log.d(TAG, " _id : " + partId + "\n charset : " + charset + "\n filename : " + filename + "\n text : " + text + "\n cid : " + cid); } part = message.addMimePart(); part.mContentId = cid; try { if (text.equalsIgnoreCase("yes")) { part.mData = body.getBytes("UTF-8"); part.mCharsetName = "utf-8"; } else { part.mData = readRawDataPart( Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE_PART), partId); if (charset != null) { part.mCharsetName = CharacterSets.getMimeName(Integer.parseInt(charset)); } } } catch (NumberFormatException e) { Log.d(TAG, "extractIMParts", e); part.mData = null; part.mCharsetName = null; } catch (UnsupportedEncodingException e) { Log.d(TAG, "extractIMParts", e); part.mData = null; part.mCharsetName = null; } part.mFileName = filename; } while (c.moveToNext()); } } finally { if (c != null) { c.close(); } } message.updateCharset(); } /** * * @param id the content provider id for the message to fetch. * @param appParams The application parameter object received from the client. * @return a byte[] containing the utf-8 encoded bMessage to send to the client. * @throws UnsupportedEncodingException if UTF-8 is not supported, * which is guaranteed to be supported on an android device */ public byte[] getMmsMessage(long id, BluetoothMapAppParams appParams) throws UnsupportedEncodingException { int msgBox, threadId; if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) { throw new IllegalArgumentException( "MMS charset native not allowed for MMS" + " - must be utf-8"); } BluetoothMapbMessageMime message = new BluetoothMapbMessageMime(); Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null); try { if (c != null && c.moveToFirst()) { message.setType(TYPE.MMS); message.setVersionString(mMessageVersion); // The MMS info: String read = c.getString(c.getColumnIndex(Mms.READ)); if (read.equalsIgnoreCase("1")) { message.setStatus(true); } else { message.setStatus(false); } msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID)); message.setFolder(getFolderName(msgBox, threadId)); message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT))); message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID))); message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE))); message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L); message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) != 0); message.setIncludeAttachments(appParams.getAttachment() != 0); // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is // The parts extractMmsParts(id, message); // The addresses extractMmsAddresses(id, message); return message.encode(); } } finally { if (c != null) { c.close(); } } return message.encode(); } /** * * @param id the content provider id for the message to fetch. * @param appParams The application parameter object received from the client. * @return a byte[] containing the utf-8 encoded bMessage to send to the client. * @throws UnsupportedEncodingException if UTF-8 is not supported, * which is guaranteed to be supported on an android device */ public byte[] getEmailMessage(long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement currentFolder) throws UnsupportedEncodingException { // Log print out of application parameters set if (D && appParams != null) { Log.d(TAG, "TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() + ", Charset = " + appParams.getCharset() + ", FractionRequest = " + appParams.getFractionRequest()); } // Throw exception if requester NATIVE charset for Email // Exception is caught by MapObexServer sendGetMessageResp if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) { throw new IllegalArgumentException("EMAIL charset not UTF-8"); } BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail(); Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null); try { if (c != null && c.moveToFirst()) { BluetoothMapFolderElement folderElement; FileInputStream is = null; ParcelFileDescriptor fd = null; try { // Handle fraction requests int fractionRequest = appParams.getFractionRequest(); if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { // Fraction requested if (V) { String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT"; Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr + " - send compete message"); } // Check if message is complete and if not - request message from server if (!c.getString(c.getColumnIndex( BluetoothMapContract.MessageColumns.RECEPTION_STATE)) .equalsIgnoreCase(BluetoothMapContract.RECEPTION_STATE_COMPLETE)) { // TODO: request message from server Log.w(TAG, "getEmailMessage - receptionState not COMPLETE - Not " + "Implemented!"); } } // Set read status: String read = c.getString( c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ)); if (read != null && read.equalsIgnoreCase("1")) { message.setStatus(true); } else { message.setStatus(false); } // Set message type: message.setType(TYPE.EMAIL); message.setVersionString(mMessageVersion); // Set folder: long folderId = c.getLong( c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID)); folderElement = currentFolder.getFolderById(folderId); message.setCompleteFolder(folderElement.getFullPath()); // Set recipient: String nameEmail = c.getString( c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST)); Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail); if (tokens.length != 0) { if (D) { Log.d(TAG, "Recipient count= " + tokens.length); } int i = 0; while (i < tokens.length) { if (V) { Log.d(TAG, "Recipient = " + tokens[i].toString()); } String[] emails = new String[1]; emails[0] = tokens[i].getAddress(); String name = tokens[i].getName(); message.addRecipient(name, name, null, emails, null, null); i++; } } // Set originator: nameEmail = c.getString( c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST)); tokens = Rfc822Tokenizer.tokenize(nameEmail); if (tokens.length != 0) { if (D) { Log.d(TAG, "Originator count= " + tokens.length); } int i = 0; while (i < tokens.length) { if (V) { Log.d(TAG, "Originator = " + tokens[i].toString()); } String[] emails = new String[1]; emails[0] = tokens[i].getAddress(); String name = tokens[i].getName(); message.addOriginator(name, name, null, emails, null, null); i++; } } } finally { if (c != null) { c.close(); } } // Find out if we get attachments String attStr = (appParams.getAttachment() == 0) ? "/" + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : ""; Uri uri = Uri.parse(contentUri + "/" + id + attStr); // Get email message body content int count = 0; try { fd = mResolver.openFileDescriptor(uri, "r"); is = new FileInputStream(fd.getFileDescriptor()); StringBuilder email = new StringBuilder(""); byte[] buffer = new byte[1024]; while ((count = is.read(buffer)) != -1) { // TODO: Handle breaks within a UTF8 character email.append(new String(buffer, 0, count)); if (V) { Log.d(TAG, "Email part = " + new String(buffer, 0, count) + " count=" + count); } } // Set email message body: message.setEmailBody(email.toString()); } catch (FileNotFoundException e) { Log.w(TAG, e); } catch (NullPointerException e) { Log.w(TAG, e); } catch (IOException e) { Log.w(TAG, e); } finally { try { if (is != null) { is.close(); } } catch (IOException e) { } try { if (fd != null) { fd.close(); } } catch (IOException e) { } } return message.encode(); } } finally { if (c != null) { c.close(); } } throw new IllegalArgumentException("EMAIL handle not found"); } /** * * @param id the content provider id for the message to fetch. * @param appParams The application parameter object received from the client. * @return a byte[] containing the UTF-8 encoded bMessage to send to the client. * @throws UnsupportedEncodingException if UTF-8 is not supported, * which is guaranteed to be supported on an android device */ /** * * @param id the content provider id for the message to fetch. * @param appParams The application parameter object received from the client. * @return a byte[] containing the utf-8 encoded bMessage to send to the client. * @throws UnsupportedEncodingException if UTF-8 is not supported, * which is guaranteed to be supported on an android device */ public byte[] getIMMessage(long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement) throws UnsupportedEncodingException { long threadId, folderId; if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) { throw new IllegalArgumentException( "IM charset native not allowed for IM - must be utf-8"); } BluetoothMapbMessageMime message = new BluetoothMapbMessageMime(); Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE); Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null); Cursor contacts = null; try { if (c != null && c.moveToFirst()) { message.setType(TYPE.IM); message.setVersionString(mMessageVersion); // The IM message info: int read = c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ)); if (read == 1) { message.setStatus(true); } else { message.setStatus(false); } threadId = c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID)); folderId = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID)); folderElement = folderElement.getFolderById(folderId); message.setCompleteFolder(folderElement.getFullPath()); message.setSubject( c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT))); message.setMessageId( c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID))); message.setDate( c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE))); message.setTextOnly(c.getInt( c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE)) == 0); message.setIncludeAttachments(appParams.getAttachment() != 0); // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is // The parts //FIXME use the parts when ready - until then use the body column for text-only // extractIMParts(id, message); //FIXME next few lines are temporary code MimePart part = message.addMimePart(); part.mData = c.getString((c.getColumnIndex(BluetoothMapContract.MessageColumns.BODY))) .getBytes("UTF-8"); part.mCharsetName = "utf-8"; part.mContentId = "0"; part.mContentType = "text/plain"; message.updateCharset(); // FIXME end temp code Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT); contacts = mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION, BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = " + threadId, null, null); // TODO this will not work for group-chats if (contacts != null && contacts.moveToFirst()) { String name = contacts.getString( contacts.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME)); String[] btUid = new String[1]; btUid[0] = contacts.getString(contacts.getColumnIndex( BluetoothMapContract.ConvoContactColumns.X_BT_UID)); String nickname = contacts.getString(contacts.getColumnIndex( BluetoothMapContract.ConvoContactColumns.NICKNAME)); String[] btUci = new String[1]; String[] btOwnUci = new String[1]; btOwnUci[0] = mAccount.getUciFull(); btUci[0] = contacts.getString( contacts.getColumnIndex(BluetoothMapContract.ConvoContactColumns.UCI)); if (folderId == BluetoothMapContract.FOLDER_ID_SENT || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) { message.addRecipient(nickname, name, null, null, btUid, btUci); message.addOriginator(null, btOwnUci); } else { message.addOriginator(nickname, name, null, null, btUid, btUci); message.addRecipient(null, btOwnUci); } } return message.encode(); } } finally { if (c != null) { c.close(); } if (contacts != null) { contacts.close(); } } throw new IllegalArgumentException("IM handle not found"); } public void setRemoteFeatureMask(int featureMask) { this.mRemoteFeatureMask = featureMask; if (V) { Log.d(TAG, "setRemoteFeatureMask"); } if ((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) { if (V) { Log.d(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11"); } this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11; } } public int getRemoteFeatureMask() { return this.mRemoteFeatureMask; } HashMap getSmsMmsConvoList() { return mMasInstance.getSmsMmsConvoList(); } void setSmsMmsConvoList(HashMap smsMmsConvoList) { mMasInstance.setSmsMmsConvoList(smsMmsConvoList); } HashMap getImEmailConvoList() { return mMasInstance.getImEmailConvoList(); } void setImEmailConvoList(HashMap imEmailConvoList) { mMasInstance.setImEmailConvoList(imEmailConvoList); } }