blob: 2cda124c37bfcd49851ab2c7f24109983c44459b [file] [log] [blame]
/*
* 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 = 0x00020000;
private static final int MASK_CONVERSATION_ID = 0x00040000;
private static final int MASK_CONVERSATION_NAME = 0x00080000;
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 = 0x00400000;
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<String> 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<String> 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();
fi.mPhoneAlphaTag = tm.getLine1AlphaTag();
}
}
/**
* 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<BluetoothMapMessageListingElement> 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);
setFolderType(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<String> selectionArgs = new ArrayList<String>(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<BluetoothMapConvoListingElement> 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<Long, BluetoothMapConvoListingElement> newList =
new HashMap<Long, BluetoothMapConvoListingElement>(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<Long, BluetoothMapConvoListingElement> newList =
new HashMap<Long, BluetoothMapConvoListingElement>(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(msgBody, phone, time));
} else {
message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(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<Long, BluetoothMapConvoListingElement> getSmsMmsConvoList() {
return mMasInstance.getSmsMmsConvoList();
}
void setSmsMmsConvoList(HashMap<Long, BluetoothMapConvoListingElement> smsMmsConvoList) {
mMasInstance.setSmsMmsConvoList(smsMmsConvoList);
}
HashMap<Long, BluetoothMapConvoListingElement> getImEmailConvoList() {
return mMasInstance.getImEmailConvoList();
}
void setImEmailConvoList(HashMap<Long, BluetoothMapConvoListingElement> imEmailConvoList) {
mMasInstance.setImEmailConvoList(imEmailConvoList);
}
}