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