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