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                     // If a draft message has no recipient, it has no thread ID
    956                     // hence threadIdStr could possibly be null
    957                     if (threadIdStr != null) {
    958                         address = getCanonicalAddressSms(mResolver, Integer.valueOf(threadIdStr));
    959                     }
    960                     if(V)  Log.v(TAG, "threadId = " + threadIdStr + " adress:" + address +"\n");
    961                 }
    962             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
    963                 long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
    964                 address = getAddressMms(mResolver, id, MMS_TO);
    965             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
    966                 /* Might be another way to handle addresses */
    967                 address = getRecipientAddressingEmail(e, c, fi);
    968             }
    969             if (V) Log.v(TAG, "setRecipientAddressing: " + address);
    970             if(address == null)
    971                 address = "";
    972             e.setRecipientAddressing(address);
    973         }
    974     }
    975 
    976     private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c,
    977         FilterInfo fi, BluetoothMapAppParams ap) {
    978         if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) {
    979             String name = null;
    980             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
    981                 int msgType = c.getInt(fi.mSmsColType);
    982                 if (msgType != 1) {
    983                     String phone = c.getString(fi.mSmsColAddress);
    984                     if (phone != null && !phone.isEmpty())
    985                         name = getContactNameFromPhone(phone, mResolver);
    986                 } else {
    987                     name = fi.mPhoneAlphaTag;
    988                 }
    989             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
    990                 long id = c.getLong(fi.mMmsColId);
    991                 String phone;
    992                 if(e.getRecipientAddressing() != null){
    993                     phone = getAddressMms(mResolver, id, MMS_TO);
    994                 } else {
    995                     phone = e.getRecipientAddressing();
    996                 }
    997                 if (phone != null && !phone.isEmpty())
    998                     name = getContactNameFromPhone(phone, mResolver);
    999             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
   1000                 /* Might be another way to handle address and names */
   1001                 name = getRecipientNameEmail(e,c,fi);
   1002             }
   1003             if (V) Log.v(TAG, "setRecipientName: " + name);
   1004             if(name == null)
   1005                 name = "";
   1006             e.setRecipientName(name);
   1007         }
   1008     }
   1009 
   1010     private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c,
   1011             FilterInfo fi, BluetoothMapAppParams ap) {
   1012         if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
   1013             String address = "";
   1014             String tempAddress;
   1015             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1016                 int msgType = c.getInt(fi.mSmsColType);
   1017                 if (msgType == 1) { // INBOX
   1018                     tempAddress = c.getString(fi.mSmsColAddress);
   1019                 } else {
   1020                     tempAddress = fi.mPhoneNum;
   1021                 }
   1022                 if(tempAddress == null) {
   1023                     /* This can only happen on devices with no SIM -
   1024                        hence will typically not have any SMS messages. */
   1025                 } else {
   1026                     address = PhoneNumberUtils.extractNetworkPortion(tempAddress);
   1027                     /* extractNetworkPortion can return N if the number is a service "number" =
   1028                      * a string with the a name in (i.e. "Some-Tele-company" would return N
   1029                      * because of the N in compaNy)
   1030                      * Hence we need to check if the number is actually a string with alpha chars.
   1031                      * */
   1032                     Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress).matches(
   1033                             "[0-9]*[a-zA-Z]+[0-9]*");
   1034 
   1035                     if(address == null || address.length() < 2 || alpha) {
   1036                         address = tempAddress; // if the number is a service acsii text just use it
   1037                     }
   1038                 }
   1039             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1040                 long id = c.getLong(fi.mMmsColId);
   1041                 tempAddress = getAddressMms(mResolver, id, MMS_FROM);
   1042                 address = PhoneNumberUtils.extractNetworkPortion(tempAddress);
   1043                 if(address == null || address.length() < 1){
   1044                     address = tempAddress; // if the number is a service acsii text just use it
   1045                 }
   1046             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* ||
   1047                        fi.mMsgType == FilterInfo.TYPE_IM*/) {
   1048                 String nameEmail = c.getString(fi.mMessageColFromAddress);
   1049                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
   1050                 if (tokens.length != 0) {
   1051                     if(D) Log.d(TAG, "Originator count= " + tokens.length);
   1052                     int i = 0;
   1053                     boolean first = true;
   1054                     while (i < tokens.length) {
   1055                         if(V) Log.d(TAG, "SenderAddress = " + tokens[i].toString());
   1056                         String[] emails = new String[1];
   1057                         emails[0] = tokens[i].getAddress();
   1058                         String name = tokens[i].getName();
   1059                         if(!first) address += "; "; //Delimiter
   1060                         address += emails[0];
   1061                         first = false;
   1062                         i++;
   1063                     }
   1064                 }
   1065             } else if(fi.mMsgType == FilterInfo.TYPE_IM) {
   1066                 // TODO: For IM we add the contact ID in the addressing
   1067                 long contact_id = c.getLong(fi.mMessageColFromAddress);
   1068                 // TODO: This is a BAD hack, that we map the contact ID to a conversation ID!!!
   1069                 //       We need to reach a conclusion on what to do
   1070                 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
   1071                 Cursor contacts = mResolver.query(contactsUri,
   1072                                            BluetoothMapContract.BT_CONTACT_PROJECTION,
   1073                                            BluetoothMapContract.ConvoContactColumns.CONVO_ID
   1074                                            + " = " + contact_id, null, null);
   1075                 try {
   1076                     // TODO this will not work for group-chats
   1077                     if(contacts != null && contacts.moveToFirst()){
   1078                         address = contacts.getString(
   1079                                 contacts.getColumnIndex(
   1080                                         BluetoothMapContract.ConvoContactColumns.UCI));
   1081                     }
   1082                 } finally {
   1083                     if (contacts != null) contacts.close();
   1084                 }
   1085 
   1086             }
   1087             if (V) Log.v(TAG, "setSenderAddressing: " + address);
   1088             if(address == null)
   1089                 address = "";
   1090             e.setSenderAddressing(address);
   1091         }
   1092     }
   1093 
   1094     private void setSenderName(BluetoothMapMessageListingElement e, Cursor c,
   1095             FilterInfo fi, BluetoothMapAppParams ap) {
   1096         if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
   1097             String name = "";
   1098             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1099                 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
   1100                 if (msgType == 1) {
   1101                     String phone = c.getString(fi.mSmsColAddress);
   1102                     if (phone != null && !phone.isEmpty())
   1103                         name = getContactNameFromPhone(phone, mResolver);
   1104                 } else {
   1105                     name = fi.mPhoneAlphaTag;
   1106                 }
   1107             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1108                 long id = c.getLong(fi.mMmsColId);
   1109                 String phone;
   1110                 if(e.getSenderAddressing() != null){
   1111                     phone = getAddressMms(mResolver, id, MMS_FROM);
   1112                 } else {
   1113                     phone = e.getSenderAddressing();
   1114                 }
   1115                 if (phone != null && !phone.isEmpty() )
   1116                     name = getContactNameFromPhone(phone, mResolver);
   1117             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/*  ||
   1118                        fi.mMsgType == FilterInfo.TYPE_IM*/) {
   1119                 String nameEmail = c.getString(fi.mMessageColFromAddress);
   1120                 Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
   1121                 if (tokens.length != 0) {
   1122                     if(D) Log.d(TAG, "Originator count= " + tokens.length);
   1123                     int i = 0;
   1124                     boolean first = true;
   1125                     while (i < tokens.length) {
   1126                         if(V) Log.d(TAG, "senderName = " + tokens[i].toString());
   1127                         String[] emails = new String[1];
   1128                         emails[0] = tokens[i].getAddress();
   1129                         String nameIn = tokens[i].getName();
   1130                         if(!first) name += "; "; //Delimiter
   1131                         name += nameIn;
   1132                         first = false;
   1133                         i++;
   1134                     }
   1135                 }
   1136             } else if(fi.mMsgType == FilterInfo.TYPE_IM) {
   1137                 // For IM we add the contact ID in the addressing
   1138                 long contact_id = c.getLong(fi.mMessageColFromAddress);
   1139                 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
   1140                 Cursor contacts = mResolver.query(contactsUri,
   1141                                            BluetoothMapContract.BT_CONTACT_PROJECTION,
   1142                                            BluetoothMapContract.ConvoContactColumns.CONVO_ID
   1143                                            + " = " + contact_id, null, null);
   1144                 try {
   1145                     // TODO this will not work for group-chats
   1146                     if(contacts != null && contacts.moveToFirst()){
   1147                         name = contacts.getString(
   1148                                 contacts.getColumnIndex(
   1149                                         BluetoothMapContract.ConvoContactColumns.NAME));
   1150                     }
   1151                 } finally {
   1152                     if (contacts != null) contacts.close();
   1153                 }
   1154             }
   1155             if (V) Log.v(TAG, "setSenderName: " + name);
   1156             if(name == null)
   1157                 name = "";
   1158             e.setSenderName(name);
   1159         }
   1160     }
   1161 
   1162 
   1163 
   1164 
   1165     private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
   1166             FilterInfo fi, BluetoothMapAppParams ap) {
   1167         if ((ap.getParameterMask() & MASK_DATETIME) != 0) {
   1168             long date = 0;
   1169             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1170                 date = c.getLong(fi.mSmsColDate);
   1171             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1172                 /* Use Mms.DATE for all messages. Although contract class states */
   1173                 /* Mms.DATE_SENT are for outgoing messages. But that is not working. */
   1174                 date = c.getLong(fi.mMmsColDate) * 1000L;
   1175 
   1176                 /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */
   1177                 /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */
   1178                 /*     date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */
   1179                 /* } else { */
   1180                 /*     date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
   1181                 /* } */
   1182             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
   1183                        fi.mMsgType == FilterInfo.TYPE_IM) {
   1184                 date = c.getLong(fi.mMessageColDate);
   1185             }
   1186             e.setDateTime(date);
   1187         }
   1188     }
   1189 
   1190 
   1191     private void setLastActivity(BluetoothMapConvoListingElement e, Cursor c,
   1192             FilterInfo fi, BluetoothMapAppParams ap) {
   1193         long date = 0;
   1194         if (fi.mMsgType == FilterInfo.TYPE_SMS ||
   1195                 fi.mMsgType == FilterInfo.TYPE_MMS ) {
   1196             date = c.getLong(MMS_SMS_THREAD_COL_DATE);
   1197         } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
   1198                 fi.mMsgType == FilterInfo.TYPE_IM) {
   1199             date = c.getLong(fi.mConvoColLastActivity);
   1200         }
   1201         e.setLastActivity(date);
   1202         if (V) Log.v(TAG, "setDateTime: " + e.getLastActivityString());
   1203 
   1204     }
   1205 
   1206     static public String getTextPartsMms(ContentResolver r, long id) {
   1207         String text = "";
   1208         String selection = new String("mid=" + id);
   1209         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part");
   1210         Uri uriAddress = Uri.parse(uriStr);
   1211         // TODO: maybe use a projection with only "ct" and "text"
   1212         Cursor c = r.query(uriAddress, null, selection,
   1213             null, null);
   1214         try {
   1215             if (c != null && c.moveToFirst()) {
   1216                 do {
   1217                     String ct = c.getString(c.getColumnIndex("ct"));
   1218                     if (ct.equals("text/plain")) {
   1219                         String part = c.getString(c.getColumnIndex("text"));
   1220                         if(part != null) {
   1221                             text += part;
   1222                         }
   1223                     }
   1224                 } while(c.moveToNext());
   1225             }
   1226         } finally {
   1227             if (c != null) c.close();
   1228         }
   1229 
   1230         return text;
   1231     }
   1232 
   1233     private void setSubject(BluetoothMapMessageListingElement e, Cursor c,
   1234             FilterInfo fi, BluetoothMapAppParams ap) {
   1235         String subject = "";
   1236         int subLength = ap.getSubjectLength();
   1237         if(subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1238             subLength = 256;
   1239 
   1240         if ((ap.getParameterMask() & MASK_SUBJECT) != 0) {
   1241             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1242                 subject = c.getString(fi.mSmsColSubject);
   1243             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1244                 subject = c.getString(fi.mMmsColSubject);
   1245                 if (subject == null || subject.length() == 0) {
   1246                     /* Get subject from mms text body parts - if any exists */
   1247                     long id = c.getLong(fi.mMmsColId);
   1248                     subject = getTextPartsMms(mResolver, id);
   1249                 }
   1250             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL  ||
   1251                        fi.mMsgType == FilterInfo.TYPE_IM) {
   1252                 subject = c.getString(fi.mMessageColSubject);
   1253             }
   1254             if (subject != null && subject.length() > subLength) {
   1255                 subject = subject.substring(0, subLength);
   1256             } else if (subject == null ) {
   1257                 subject = "";
   1258             }
   1259             if (V) Log.d(TAG, "setSubject: " + subject);
   1260             e.setSubject(subject);
   1261         }
   1262     }
   1263 
   1264     private void setHandle(BluetoothMapMessageListingElement e, Cursor c,
   1265             FilterInfo fi, BluetoothMapAppParams ap) {
   1266         long handle = -1;
   1267         if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1268             handle = c.getLong(fi.mSmsColId);
   1269         } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1270             handle = c.getLong(fi.mMmsColId);
   1271         } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
   1272                    fi.mMsgType == FilterInfo.TYPE_IM) {
   1273             handle = c.getLong(fi.mMessageColId);
   1274         }
   1275         if (V) Log.d(TAG, "setHandle: " + handle );
   1276         e.setHandle(handle);
   1277     }
   1278 
   1279     private BluetoothMapMessageListingElement element(Cursor c, FilterInfo fi,
   1280             BluetoothMapAppParams ap) {
   1281         BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement();
   1282         setHandle(e, c, fi, ap);
   1283         setDateTime(e, c, fi, ap);
   1284         e.setType(getType(c, fi), ((ap.getParameterMask() & MASK_TYPE) != 0) ? true : false);
   1285         setRead(e, c, fi, ap);
   1286         // we set number and name for sender/recipient later
   1287         // they require lookup on contacts so no need to
   1288         // do it for all elements unless they are to be used.
   1289         e.setCursorIndex(c.getPosition());
   1290         return e;
   1291     }
   1292 
   1293     private BluetoothMapConvoListingElement createConvoElement(Cursor c, FilterInfo fi,
   1294             BluetoothMapAppParams ap) {
   1295         BluetoothMapConvoListingElement e = new BluetoothMapConvoListingElement();
   1296         setLastActivity(e, c, fi, ap);
   1297         e.setType(getType(c, fi));
   1298 //        setConvoRead(e, c, fi, ap);
   1299         e.setCursorIndex(c.getPosition());
   1300         return e;
   1301     }
   1302 
   1303     /* TODO: Change to use SmsMmsContacts.getContactNameFromPhone() with proper use of
   1304      *       caching. */
   1305     public static String getContactNameFromPhone(String phone, ContentResolver resolver) {
   1306         String name = null;
   1307         //Handle possible exception for empty phone address
   1308         if (TextUtils.isEmpty(phone)) {
   1309             return name;
   1310         }
   1311 
   1312         Uri uri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
   1313                 Uri.encode(phone));
   1314 
   1315         String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
   1316         String selection = Contacts.IN_VISIBLE_GROUP + "=1";
   1317         String orderBy = Contacts.DISPLAY_NAME + " ASC";
   1318         Cursor c = null;
   1319         try {
   1320             c = resolver.query(uri, projection, selection, null, orderBy);
   1321             if(c != null) {
   1322                 int colIndex = c.getColumnIndex(Contacts.DISPLAY_NAME);
   1323                 if (c.getCount() >= 1) {
   1324                     c.moveToFirst();
   1325                     name = c.getString(colIndex);
   1326                 }
   1327             }
   1328         } finally {
   1329             if(c != null) c.close();
   1330         }
   1331         return name;
   1332     }
   1333     /**
   1334      * Get SMS RecipientAddresses for DRAFT folder based on threadId
   1335      *
   1336     */
   1337     static public String getCanonicalAddressSms(ContentResolver r,  int threadId) {
   1338        String [] RECIPIENT_ID_PROJECTION = { Threads.RECIPIENT_IDS };
   1339         /*
   1340          1. Get Recipient Ids from Threads.CONTENT_URI
   1341          2. Get Recipient Address for corresponding Id from canonical-addresses table.
   1342         */
   1343 
   1344         //Uri sAllCanonical = Uri.parse("content://mms-sms/canonical-addresses");
   1345         Uri sAllCanonical =
   1346                 MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
   1347         Uri sAllThreadsUri =
   1348                 Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
   1349         Cursor cr = null;
   1350         String recipientAddress = "";
   1351         String recipientIds = null;
   1352         String whereClause = "_id="+threadId;
   1353         if (V) Log.v(TAG, "whereClause is "+ whereClause);
   1354         try {
   1355             cr = r.query(sAllThreadsUri, RECIPIENT_ID_PROJECTION, whereClause, null, null);
   1356             if (cr != null && cr.moveToFirst()) {
   1357                 recipientIds = cr.getString(0);
   1358                 if (V) Log.v(TAG, "cursor.getCount(): " + cr.getCount() + "recipientIds: "
   1359                         + recipientIds + "selection: "+ whereClause );
   1360             }
   1361         } finally {
   1362             if(cr != null) {
   1363                 cr.close();
   1364                 cr = null;
   1365             }
   1366         }
   1367         if (V) Log.v(TAG, "recipientIds with spaces: "+ recipientIds +"\n");
   1368         if(recipientIds != null) {
   1369             String recipients[] = null;
   1370             whereClause = "";
   1371             recipients = recipientIds.split(" ");
   1372             for (String id: recipients) {
   1373                 if(whereClause.length() != 0)
   1374                     whereClause +=" OR ";
   1375                 whereClause +="_id="+id;
   1376             }
   1377             if (V) Log.v(TAG, "whereClause is "+ whereClause);
   1378             try {
   1379                 cr = r.query(sAllCanonical , null, whereClause, null, null);
   1380                 if (cr != null && cr.moveToFirst()) {
   1381                     do {
   1382                         //TODO: Multiple Recipeints are appended with ";" for now.
   1383                         if(recipientAddress.length() != 0 )
   1384                            recipientAddress+=";";
   1385                         recipientAddress += cr.getString(
   1386                                 cr.getColumnIndex(CanonicalAddressesColumns.ADDRESS));
   1387                     } while(cr.moveToNext());
   1388                 }
   1389            } finally {
   1390                if(cr != null)
   1391                    cr.close();
   1392            }
   1393         }
   1394 
   1395         if(V) Log.v(TAG,"Final recipientAddress : "+ recipientAddress);
   1396         return recipientAddress;
   1397      }
   1398 
   1399     static public String getAddressMms(ContentResolver r, long id, int type) {
   1400         String selection = new String("msg_id=" + id + " AND type=" + type);
   1401         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
   1402         Uri uriAddress = Uri.parse(uriStr);
   1403         String addr = null;
   1404         String[] projection = {Mms.Addr.ADDRESS};
   1405         Cursor c = null;
   1406         try {
   1407             c = r.query(uriAddress, projection, selection, null, null); // TODO: Add projection
   1408             int colIndex = c.getColumnIndex(Mms.Addr.ADDRESS);
   1409             if (c != null) {
   1410                 if(c.moveToFirst()) {
   1411                     addr = c.getString(colIndex);
   1412                     if(addr.equals(INSERT_ADDRES_TOKEN)) {
   1413                         addr  = "";
   1414                     }
   1415                 }
   1416             }
   1417         } finally {
   1418             if (c != null) c.close();
   1419         }
   1420         return addr;
   1421     }
   1422 
   1423     /**
   1424      * Matching functions for originator and recipient for MMS
   1425      * @return true if found a match
   1426      */
   1427     private boolean matchRecipientMms(Cursor c, FilterInfo fi, String recip) {
   1428         boolean res;
   1429         long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
   1430         String phone = getAddressMms(mResolver, id, MMS_TO);
   1431         if (phone != null && phone.length() > 0) {
   1432             if (phone.matches(recip)) {
   1433                 if (V) Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone);
   1434                 res = true;
   1435             } else {
   1436                 String name = getContactNameFromPhone(phone, mResolver);
   1437                 if (name != null && name.length() > 0 && name.matches(recip)) {
   1438                     if (V) Log.v(TAG, "matchRecipientMms: match recipient name = " + name);
   1439                     res = true;
   1440                 } else {
   1441                     res = false;
   1442                 }
   1443             }
   1444         } else {
   1445             res = false;
   1446         }
   1447         return res;
   1448     }
   1449 
   1450     private boolean matchRecipientSms(Cursor c, FilterInfo fi, String recip) {
   1451         boolean res;
   1452         int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
   1453         if (msgType == 1) {
   1454             String phone = fi.mPhoneNum;
   1455             String name = fi.mPhoneAlphaTag;
   1456             if (phone != null && phone.length() > 0 && phone.matches(recip)) {
   1457                 if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
   1458                 res = true;
   1459             } else if (name != null && name.length() > 0 && name.matches(recip)) {
   1460                 if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
   1461                 res = true;
   1462             } else {
   1463                 res = false;
   1464             }
   1465         } else {
   1466             String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
   1467             if (phone != null && phone.length() > 0) {
   1468                 if (phone.matches(recip)) {
   1469                     if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
   1470                     res = true;
   1471                 } else {
   1472                     String name = getContactNameFromPhone(phone, mResolver);
   1473                     if (name != null && name.length() > 0 && name.matches(recip)) {
   1474                         if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
   1475                         res = true;
   1476                     } else {
   1477                         res = false;
   1478                     }
   1479                 }
   1480             } else {
   1481                 res = false;
   1482             }
   1483         }
   1484         return res;
   1485     }
   1486 
   1487     private boolean matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
   1488         boolean res;
   1489         String recip = ap.getFilterRecipient();
   1490         if (recip != null && recip.length() > 0) {
   1491             recip = recip.replace("*", ".*");
   1492             recip = ".*" + recip + ".*";
   1493             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1494                 res = matchRecipientSms(c, fi, recip);
   1495             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1496                 res = matchRecipientMms(c, fi, recip);
   1497             } else {
   1498                 if (D) Log.d(TAG, "matchRecipient: Unknown msg type: " + fi.mMsgType);
   1499                 res = false;
   1500             }
   1501         } else {
   1502             res = true;
   1503         }
   1504         return res;
   1505     }
   1506 
   1507     private boolean matchOriginatorMms(Cursor c, FilterInfo fi, String orig) {
   1508         boolean res;
   1509         long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
   1510         String phone = getAddressMms(mResolver, id, MMS_FROM);
   1511         if (phone != null && phone.length() > 0) {
   1512             if (phone.matches(orig)) {
   1513                 if (V) Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone);
   1514                 res = true;
   1515             } else {
   1516                 String name = getContactNameFromPhone(phone, mResolver);
   1517                 if (name != null && name.length() > 0 && name.matches(orig)) {
   1518                     if (V) Log.v(TAG, "matchOriginatorMms: match originator name = " + name);
   1519                     res = true;
   1520                 } else {
   1521                     res = false;
   1522                 }
   1523             }
   1524         } else {
   1525             res = false;
   1526         }
   1527         return res;
   1528     }
   1529 
   1530     private boolean matchOriginatorSms(Cursor c, FilterInfo fi, String orig) {
   1531         boolean res;
   1532         int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
   1533         if (msgType == 1) {
   1534             String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
   1535             if (phone !=null && phone.length() > 0) {
   1536                 if (phone.matches(orig)) {
   1537                     if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
   1538                     res = true;
   1539                 } else {
   1540                     String name = getContactNameFromPhone(phone, mResolver);
   1541                     if (name != null && name.length() > 0 && name.matches(orig)) {
   1542                         if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
   1543                         res = true;
   1544                     } else {
   1545                         res = false;
   1546                     }
   1547                 }
   1548             } else {
   1549                 res = false;
   1550             }
   1551         } else {
   1552             String phone = fi.mPhoneNum;
   1553             String name = fi.mPhoneAlphaTag;
   1554             if (phone != null && phone.length() > 0 && phone.matches(orig)) {
   1555                 if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
   1556                 res = true;
   1557             } else if (name != null && name.length() > 0 && name.matches(orig)) {
   1558                 if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
   1559                 res = true;
   1560             } else {
   1561                 res = false;
   1562             }
   1563         }
   1564         return res;
   1565     }
   1566 
   1567    private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
   1568         boolean res;
   1569         String orig = ap.getFilterOriginator();
   1570         if (orig != null && orig.length() > 0) {
   1571             orig = orig.replace("*", ".*");
   1572             orig = ".*" + orig + ".*";
   1573             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1574                 res = matchOriginatorSms(c, fi, orig);
   1575             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1576                 res = matchOriginatorMms(c, fi, orig);
   1577             } else {
   1578                 if(D) Log.d(TAG, "matchOriginator: Unknown msg type: " + fi.mMsgType);
   1579                 res = false;
   1580             }
   1581         } else {
   1582             res = true;
   1583         }
   1584         return res;
   1585     }
   1586 
   1587     private boolean matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
   1588         if (matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap)) {
   1589             return true;
   1590         } else {
   1591             return false;
   1592         }
   1593     }
   1594 
   1595     /*
   1596      * Where filter functions
   1597      * */
   1598     private String setWhereFilterFolderTypeSms(String folder) {
   1599         String where = "";
   1600         if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) {
   1601             where = Sms.TYPE + " = 1 AND " + Sms.THREAD_ID + " <> -1";
   1602         } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) {
   1603             where = "(" + Sms.TYPE + " = 4 OR " + Sms.TYPE + " = 5 OR "
   1604                     + Sms.TYPE + " = 6) AND " + Sms.THREAD_ID + " <> -1";
   1605         } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) {
   1606             where = Sms.TYPE + " = 2 AND " + Sms.THREAD_ID + " <> -1";
   1607         } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) {
   1608             where = Sms.TYPE + " = 3 AND " +
   1609                 "(" + Sms.THREAD_ID + " IS NULL OR " + Sms.THREAD_ID + " <> -1 )";
   1610         } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) {
   1611             where = Sms.THREAD_ID + " = -1";
   1612         }
   1613 
   1614         return where;
   1615     }
   1616 
   1617     private String setWhereFilterFolderTypeMms(String folder) {
   1618         String where = "";
   1619         if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) {
   1620             where = Mms.MESSAGE_BOX + " = 1 AND " + Mms.THREAD_ID + " <> -1";
   1621         } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) {
   1622             where = Mms.MESSAGE_BOX + " = 4 AND " + Mms.THREAD_ID + " <> -1";
   1623         } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) {
   1624             where = Mms.MESSAGE_BOX + " = 2 AND " + Mms.THREAD_ID + " <> -1";
   1625         } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) {
   1626             where = Mms.MESSAGE_BOX + " = 3 AND " +
   1627                 "(" + Mms.THREAD_ID + " IS NULL OR " + Mms.THREAD_ID + " <> -1 )";
   1628         } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) {
   1629             where = Mms.THREAD_ID + " = -1";
   1630         }
   1631 
   1632         return where;
   1633     }
   1634 
   1635     private String setWhereFilterFolderTypeEmail(long folderId) {
   1636         String where = "";
   1637         if (folderId >= 0) {
   1638             where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId;
   1639         } else {
   1640             Log.e(TAG, "setWhereFilterFolderTypeEmail: not valid!" );
   1641             throw new IllegalArgumentException("Invalid folder ID");
   1642         }
   1643         return where;
   1644     }
   1645 
   1646     private String setWhereFilterFolderTypeIm(long folderId) {
   1647         String where = "";
   1648         if (folderId > BluetoothMapContract.FOLDER_ID_OTHER) {
   1649             where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId;
   1650         } else {
   1651             Log.e(TAG, "setWhereFilterFolderTypeIm: not valid!" );
   1652             throw new IllegalArgumentException("Invalid folder ID");
   1653         }
   1654         return where;
   1655     }
   1656 
   1657     private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement,
   1658                                             FilterInfo fi) {
   1659         String where = "";
   1660         if(folderElement.shouldIgnore()) {
   1661             where = "1=1";
   1662         } else {
   1663             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1664                 where = setWhereFilterFolderTypeSms(folderElement.getName());
   1665             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1666                 where = setWhereFilterFolderTypeMms(folderElement.getName());
   1667             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
   1668                 where = setWhereFilterFolderTypeEmail(folderElement.getFolderId());
   1669             } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
   1670                 where = setWhereFilterFolderTypeIm(folderElement.getFolderId());
   1671             }
   1672         }
   1673         return where;
   1674     }
   1675 
   1676     private String setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi) {
   1677         String where = "";
   1678         if (ap.getFilterReadStatus() != -1) {
   1679             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1680                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
   1681                     where = " AND " + Sms.READ + "= 0";
   1682                 }
   1683 
   1684                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
   1685                     where = " AND " + Sms.READ + "= 1";
   1686                 }
   1687             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1688                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
   1689                     where = " AND " + Mms.READ + "= 0";
   1690                 }
   1691 
   1692                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
   1693                     where = " AND " + Mms.READ + "= 1";
   1694                 }
   1695             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
   1696                        fi.mMsgType == FilterInfo.TYPE_IM) {
   1697                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
   1698                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0";
   1699                 }
   1700                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
   1701                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 1";
   1702                 }
   1703             }
   1704         }
   1705         return where;
   1706     }
   1707 
   1708     private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) {
   1709         String where = "";
   1710 
   1711         if ((ap.getFilterPeriodBegin() != -1)) {
   1712             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1713                 where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin();
   1714             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1715                 where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L);
   1716             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
   1717                        fi.mMsgType == FilterInfo.TYPE_IM) {
   1718                 where = " AND " + BluetoothMapContract.MessageColumns.DATE +
   1719                         " >= " + (ap.getFilterPeriodBegin());
   1720             }
   1721         }
   1722 
   1723         if ((ap.getFilterPeriodEnd() != -1)) {
   1724             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1725                 where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd();
   1726             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1727                 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
   1728             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
   1729                        fi.mMsgType == FilterInfo.TYPE_IM) {
   1730                 where += " AND " + BluetoothMapContract.MessageColumns.DATE +
   1731                         " < " + (ap.getFilterPeriodEnd());
   1732             }
   1733         }
   1734         return where;
   1735     }
   1736     private String setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi) {
   1737             String where = "";
   1738         if ((ap.getFilterLastActivityBegin() != -1)) {
   1739             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1740                 where = " AND " + Sms.DATE + " >= " + ap.getFilterLastActivityBegin();
   1741             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1742                 where = " AND " + Mms.DATE + " >= " + (ap.getFilterLastActivityBegin() / 1000L);
   1743             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
   1744                       fi.mMsgType == FilterInfo.TYPE_IM ) {
   1745                 where = " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY +
   1746                         " >= " + (ap.getFilterPeriodBegin());
   1747             }
   1748         }
   1749         if ((ap.getFilterLastActivityEnd() != -1)) {
   1750             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1751                 where += " AND " + Sms.DATE + " < " + ap.getFilterLastActivityEnd();
   1752             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1753                 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
   1754             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||fi.mMsgType == FilterInfo.TYPE_IM) {
   1755                 where += " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
   1756                       + " < " + (ap.getFilterLastActivityEnd());
   1757             }
   1758         }
   1759         return where;
   1760     }
   1761 
   1762 
   1763     private String setWhereFilterOriginatorEmail(BluetoothMapAppParams ap) {
   1764         String where = "";
   1765         String orig = ap.getFilterOriginator();
   1766 
   1767         /* Be aware of wild cards in the beginning of string, may not be valid? */
   1768         if (orig != null && orig.length() > 0) {
   1769             orig = orig.replace("*", "%");
   1770             where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
   1771                     + " LIKE '%" +  orig + "%'";
   1772         }
   1773         return where;
   1774     }
   1775 
   1776     private String setWhereFilterOriginatorIM(BluetoothMapAppParams ap) {
   1777         String where = "";
   1778         String orig = ap.getFilterOriginator();
   1779 
   1780         /* Be aware of wild cards in the beginning of string, may not be valid? */
   1781         if (orig != null && orig.length() > 0) {
   1782             orig = orig.replace("*", "%");
   1783             where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
   1784                     + " LIKE '%" +  orig + "%'";
   1785         }
   1786         return where;
   1787     }
   1788 
   1789     private String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) {
   1790         String where = "";
   1791         int pri = ap.getFilterPriority();
   1792         /*only MMS have priority info */
   1793         if(fi.mMsgType == FilterInfo.TYPE_MMS)
   1794         {
   1795             if(pri == 0x0002)
   1796             {
   1797                 where += " AND " + Mms.PRIORITY + "<=" +
   1798                     Integer.toString(PduHeaders.PRIORITY_NORMAL);
   1799             }else if(pri == 0x0001) {
   1800                 where += " AND " + Mms.PRIORITY + "=" +
   1801                     Integer.toString(PduHeaders.PRIORITY_HIGH);
   1802             }
   1803         }
   1804         if(fi.mMsgType == FilterInfo.TYPE_EMAIL ||
   1805            fi.mMsgType == FilterInfo.TYPE_IM)
   1806         {
   1807             if(pri == 0x0002)
   1808             {
   1809                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "!=1";
   1810             }else if(pri == 0x0001) {
   1811                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "=1";
   1812             }
   1813         }
   1814         // TODO: no priority filtering in IM
   1815         return where;
   1816     }
   1817 
   1818     private String setWhereFilterRecipientEmail(BluetoothMapAppParams ap) {
   1819         String where = "";
   1820         String recip = ap.getFilterRecipient();
   1821 
   1822         /* Be aware of wild cards in the beginning of string, may not be valid? */
   1823         if (recip != null && recip.length() > 0) {
   1824             recip = recip.replace("*", "%");
   1825             where = " AND ("
   1826             + BluetoothMapContract.MessageColumns.TO_LIST  + " LIKE '%" + recip + "%' OR "
   1827             + BluetoothMapContract.MessageColumns.CC_LIST  + " LIKE '%" + recip + "%' OR "
   1828             + BluetoothMapContract.MessageColumns.BCC_LIST + " LIKE '%" + recip + "%' )";
   1829         }
   1830         return where;
   1831     }
   1832 
   1833     private String setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi) {
   1834         String where = "";
   1835         long id = -1;
   1836         String msgHandle = ap.getFilterMsgHandleString();
   1837         if(msgHandle != null) {
   1838             id = BluetoothMapUtils.getCpHandle(msgHandle);
   1839             if(D)Log.d(TAG,"id: " + id);
   1840         }
   1841         if(id != -1) {
   1842             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1843                where = " AND " + Sms._ID + " = " + id;
   1844             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1845                 where = " AND " + Mms._ID + " = " + id;
   1846             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
   1847                        fi.mMsgType == FilterInfo.TYPE_IM) {
   1848                 where = " AND " + BluetoothMapContract.MessageColumns._ID + " = " + id;
   1849             }
   1850         }
   1851         return where;
   1852     }
   1853 
   1854     private String setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi) {
   1855         String where = "";
   1856         long id = -1;
   1857         String msgHandle = ap.getFilterConvoIdString();
   1858         if(msgHandle != null) {
   1859             id = BluetoothMapUtils.getMsgHandleAsLong(msgHandle);
   1860             if(D)Log.d(TAG,"id: " + id);
   1861         }
   1862         if(id > 0) {
   1863             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
   1864                where = " AND " + Sms.THREAD_ID + " = " + id;
   1865             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
   1866                 where = " AND " + Mms.THREAD_ID + " = " + id;
   1867             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
   1868                        fi.mMsgType == FilterInfo.TYPE_IM) {
   1869                 where = " AND " + BluetoothMapContract.MessageColumns.THREAD_ID + " = " + id;
   1870             }
   1871         }
   1872 
   1873         return where;
   1874     }
   1875 
   1876     private String setWhereFilter(BluetoothMapFolderElement folderElement,
   1877             FilterInfo fi, BluetoothMapAppParams ap) {
   1878         String where = "";
   1879         where += setWhereFilterFolderType(folderElement, fi);
   1880 
   1881         String msgHandleWhere = setWhereFilterMessageHandle(ap, fi);
   1882         /* if message handle filter is available, the other filters should be ignored */
   1883         if(msgHandleWhere.isEmpty()) {
   1884             where += setWhereFilterReadStatus(ap, fi);
   1885             where += setWhereFilterPriority(ap,fi);
   1886             where += setWhereFilterPeriod(ap, fi);
   1887             if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
   1888                 where += setWhereFilterOriginatorEmail(ap);
   1889                 where += setWhereFilterRecipientEmail(ap);
   1890             }
   1891             if (fi.mMsgType == FilterInfo.TYPE_IM) {
   1892                 where += setWhereFilterOriginatorIM(ap);
   1893                 // TODO: set 'where' filer recipient?
   1894             }
   1895             where += setWhereFilterThreadId(ap, fi);
   1896         } else {
   1897             where += msgHandleWhere;
   1898         }
   1899 
   1900         return where;
   1901     }
   1902 
   1903 
   1904     /* Used only for SMS/MMS */
   1905     private void setConvoWhereFilterSmsMms(StringBuilder selection, ArrayList<String> selectionArgs,
   1906             FilterInfo fi, BluetoothMapAppParams ap) {
   1907 
   1908         if (smsSelected(fi, ap) || mmsSelected(ap)) {
   1909 
   1910             // Filter Read Status
   1911             if(ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
   1912                 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_UNREAD_ONLY) != 0) {
   1913                     selection.append(" AND ").append(Threads.READ).append(" = 0");
   1914                 }
   1915                 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_READ_ONLY) != 0) {
   1916                     selection.append(" AND ").append(Threads.READ).append(" = 1");
   1917                 }
   1918             }
   1919 
   1920             // Filter time
   1921             if ((ap.getFilterLastActivityBegin() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)){
   1922                 selection.append(" AND ").append(Threads.DATE).append(" >= ")
   1923                 .append(ap.getFilterLastActivityBegin());
   1924             }
   1925             if ((ap.getFilterLastActivityEnd() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) {
   1926                 selection.append(" AND ").append(Threads.DATE).append(" <= ")
   1927                 .append(ap.getFilterLastActivityEnd());
   1928             }
   1929 
   1930             // Filter ConvoId
   1931             long convoId = -1;
   1932             if(ap.getFilterConvoId() != null) {
   1933                 convoId = ap.getFilterConvoId().getLeastSignificantBits();
   1934             }
   1935             if(convoId > 0) {
   1936                 selection.append(" AND ").append(Threads._ID).append(" = ")
   1937                 .append(Long.toString(convoId));
   1938             }
   1939         }
   1940     }
   1941 
   1942 
   1943 
   1944     /**
   1945      * Determine from application parameter if sms should be included.
   1946      * The filter mask is set for message types not selected
   1947      * @param fi
   1948      * @param ap
   1949      * @return boolean true if sms is selected, false if not
   1950      */
   1951     private boolean smsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
   1952         int msgType = ap.getFilterMessageType();
   1953         int phoneType = fi.mPhoneType;
   1954 
   1955         if (D) Log.d(TAG, "smsSelected msgType: " + msgType);
   1956 
   1957         if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1958             return true;
   1959 
   1960         if ((msgType & (BluetoothMapAppParams.FILTER_NO_SMS_CDMA
   1961                 |BluetoothMapAppParams.FILTER_NO_SMS_GSM)) == 0)
   1962             return true;
   1963 
   1964         if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0)
   1965                 && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
   1966             return true;
   1967 
   1968         if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0)
   1969                 && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
   1970             return true;
   1971 
   1972         return false;
   1973     }
   1974 
   1975     /**
   1976      * Determine from application parameter if mms should be included.
   1977      * The filter mask is set for message types not selected
   1978      * @param fi
   1979      * @param ap
   1980      * @return boolean true if mms is selected, false if not
   1981      */
   1982     private boolean mmsSelected(BluetoothMapAppParams ap) {
   1983         int msgType = ap.getFilterMessageType();
   1984 
   1985         if (D) Log.d(TAG, "mmsSelected msgType: " + msgType);
   1986 
   1987         if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1988             return true;
   1989 
   1990         if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0)
   1991             return true;
   1992 
   1993         return false;
   1994     }
   1995 
   1996     /**
   1997      * Determine from application parameter if email should be included.
   1998      * The filter mask is set for message types not selected
   1999      * @param fi
   2000      * @param ap
   2001      * @return boolean true if email is selected, false if not
   2002      */
   2003     private boolean emailSelected(BluetoothMapAppParams ap) {
   2004         int msgType = ap.getFilterMessageType();
   2005 
   2006         if (D) Log.d(TAG, "emailSelected msgType: " + msgType);
   2007 
   2008         if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   2009             return true;
   2010 
   2011         if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0)
   2012             return true;
   2013 
   2014         return false;
   2015     }
   2016 
   2017     /**
   2018      * Determine from application parameter if IM should be included.
   2019      * The filter mask is set for message types not selected
   2020      * @param fi
   2021      * @param ap
   2022      * @return boolean true if im is selected, false if not
   2023      */
   2024     private boolean imSelected(BluetoothMapAppParams ap) {
   2025         int msgType = ap.getFilterMessageType();
   2026 
   2027         if (D) Log.d(TAG, "imSelected msgType: " + msgType);
   2028 
   2029         if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   2030             return true;
   2031 
   2032         if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0)
   2033             return true;
   2034 
   2035         return false;
   2036     }
   2037 
   2038     private void setFilterInfo(FilterInfo fi) {
   2039         TelephonyManager tm =
   2040             (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
   2041         if (tm != null) {
   2042             fi.mPhoneType = tm.getPhoneType();
   2043             fi.mPhoneNum = tm.getLine1Number();
   2044             fi.mPhoneAlphaTag = tm.getLine1AlphaTag();
   2045             if (D) Log.d(TAG, "phone type = " + fi.mPhoneType +
   2046                 " phone num = " + fi.mPhoneNum +
   2047                 " phone alpha tag = " + fi.mPhoneAlphaTag);
   2048         }
   2049     }
   2050 
   2051     /**
   2052      * Get a listing of message in folder after applying filter.
   2053      * @param folder Must contain a valid folder string != null
   2054      * @param ap Parameters specifying message content and filters
   2055      * @return Listing object containing requested messages
   2056      */
   2057     public BluetoothMapMessageListing msgListing(BluetoothMapFolderElement folderElement,
   2058             BluetoothMapAppParams ap) {
   2059         if (D) Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType() );
   2060 
   2061         BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
   2062 
   2063         /* We overwrite the parameter mask here if it is 0 or not present, as this
   2064          * should cause all parameters to be included in the message list. */
   2065         if(ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
   2066                 ap.getParameterMask() == 0) {
   2067             ap.setParameterMask(PARAMETER_MASK_DEFAULT);
   2068             if (V) Log.v(TAG, "msgListing(): appParameterMask is zero or not present, " +
   2069                     "changing to default: " + ap.getParameterMask());
   2070         }
   2071         if (V) Log.v(TAG, "folderElement hasSmsMmsContent = " + folderElement.hasSmsMmsContent() +
   2072                 " folderElement.hasEmailContent = " + folderElement.hasEmailContent() +
   2073                 " folderElement.hasImContent = " + folderElement.hasImContent());
   2074 
   2075         /* Cache some info used throughout filtering */
   2076         FilterInfo fi = new FilterInfo();
   2077         setFilterInfo(fi);
   2078         Cursor smsCursor = null;
   2079         Cursor mmsCursor = null;
   2080         Cursor emailCursor = null;
   2081         Cursor imCursor = null;
   2082         String limit = "";
   2083         int countNum = ap.getMaxListCount();
   2084         int offsetNum = ap.getStartOffset();
   2085         if(ap.getMaxListCount()>0){
   2086             limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset());
   2087         }
   2088         try{
   2089             if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
   2090                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
   2091                                                  BluetoothMapAppParams.FILTER_NO_MMS|
   2092                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM|
   2093                                                  BluetoothMapAppParams.FILTER_NO_IM)||
   2094                    ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
   2095                                                  BluetoothMapAppParams.FILTER_NO_MMS|
   2096                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
   2097                                                  BluetoothMapAppParams.FILTER_NO_IM)){
   2098                     //set real limit and offset if only this type is used
   2099                     // (only if offset/limit is used)
   2100                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
   2101                     if(D) Log.d(TAG, "SMS Limit => "+limit);
   2102                     offsetNum = 0;
   2103                 }
   2104                 fi.mMsgType = FilterInfo.TYPE_SMS;
   2105                 if(ap.getFilterPriority() != 1){ /*SMS cannot have high priority*/
   2106                     String where = setWhereFilter(folderElement, fi, ap);
   2107                     if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
   2108                     smsCursor = mResolver.query(Sms.CONTENT_URI,
   2109                             SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit);
   2110                     if (smsCursor != null) {
   2111                         BluetoothMapMessageListingElement e = null;
   2112                         // store column index so we dont have to look them up anymore (optimization)
   2113                         if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
   2114                         fi.setSmsColumns(smsCursor);
   2115                         while (smsCursor.moveToNext()) {
   2116                             if (matchAddresses(smsCursor, fi, ap)) {
   2117                                 if(V) BluetoothMapUtils.printCursor(smsCursor);
   2118                                 e = element(smsCursor, fi, ap);
   2119                                 bmList.add(e);
   2120                             }
   2121                         }
   2122                     }
   2123                 }
   2124             }
   2125 
   2126             if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
   2127                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
   2128                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
   2129                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM|
   2130                                                  BluetoothMapAppParams.FILTER_NO_IM)){
   2131                     //set real limit and offset if only this type is used
   2132                     //(only if offset/limit is used)
   2133                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
   2134                     if(D) Log.d(TAG, "MMS Limit => "+limit);
   2135                     offsetNum = 0;
   2136                 }
   2137                 fi.mMsgType = FilterInfo.TYPE_MMS;
   2138                 String where = setWhereFilter(folderElement, fi, ap);
   2139                 where += " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE;
   2140                 if(!where.isEmpty()) {
   2141                     if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
   2142                     mmsCursor = mResolver.query(Mms.CONTENT_URI,
   2143                             MMS_PROJECTION, where, null, Mms.DATE + " DESC" + limit);
   2144                     if (mmsCursor != null) {
   2145                         BluetoothMapMessageListingElement e = null;
   2146                         // store column index so we dont have to look them up anymore (optimization)
   2147                         fi.setMmsColumns(mmsCursor);
   2148                         if(D) Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages.");
   2149                         while (mmsCursor.moveToNext()) {
   2150                             if (matchAddresses(mmsCursor, fi, ap)) {
   2151                                 if(V) BluetoothMapUtils.printCursor(mmsCursor);
   2152                                 e = element(mmsCursor, fi, ap);
   2153                                 bmList.add(e);
   2154                             }
   2155                         }
   2156                     }
   2157                 }
   2158             }
   2159 
   2160             if (emailSelected(ap) && folderElement.hasEmailContent()) {
   2161                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
   2162                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
   2163                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM|
   2164                                                  BluetoothMapAppParams.FILTER_NO_IM)){
   2165                     //set real limit and offset if only this type is used
   2166                     //(only if offset/limit is used)
   2167                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
   2168                     if(D) Log.d(TAG, "Email Limit => "+limit);
   2169                     offsetNum = 0;
   2170                 }
   2171                 fi.mMsgType = FilterInfo.TYPE_EMAIL;
   2172                 String where = setWhereFilter(folderElement, fi, ap);
   2173 
   2174                 if(!where.isEmpty()) {
   2175                     if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
   2176                     Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
   2177                     emailCursor = mResolver.query(contentUri,
   2178                             BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
   2179                             BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
   2180                     if (emailCursor != null) {
   2181                         BluetoothMapMessageListingElement e = null;
   2182                         // store column index so we dont have to look them up anymore (optimization)
   2183                         fi.setEmailMessageColumns(emailCursor);
   2184                         int cnt = 0;
   2185                         if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
   2186                         while (emailCursor.moveToNext()) {
   2187                             if(V) BluetoothMapUtils.printCursor(emailCursor);
   2188                             e = element(emailCursor, fi, ap);
   2189                             bmList.add(e);
   2190                         }
   2191                     //   emailCursor.close();
   2192                     }
   2193                 }
   2194             }
   2195 
   2196             if (imSelected(ap) && folderElement.hasImContent()) {
   2197                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
   2198                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
   2199                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM|
   2200                                                  BluetoothMapAppParams.FILTER_NO_EMAIL)){
   2201                     //set real limit and offset if only this type is used
   2202                     //(only if offset/limit is used)
   2203                     limit = " LIMIT " + ap.getMaxListCount() + " OFFSET "+ ap.getStartOffset();
   2204                     if(D) Log.d(TAG, "IM Limit => "+limit);
   2205                     offsetNum = 0;
   2206                 }
   2207                 fi.mMsgType = FilterInfo.TYPE_IM;
   2208                 String where = setWhereFilter(folderElement, fi, ap);
   2209                 if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
   2210 
   2211                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
   2212                 imCursor = mResolver.query(contentUri,
   2213                         BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
   2214                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
   2215                 if (imCursor != null) {
   2216                     BluetoothMapMessageListingElement e = null;
   2217                     // store column index so we dont have to look them up anymore (optimization)
   2218                     fi.setImMessageColumns(imCursor);
   2219                     if (D) Log.d(TAG, "Found " + imCursor.getCount() + " im messages.");
   2220                     while (imCursor.moveToNext()) {
   2221                         if (V) BluetoothMapUtils.printCursor(imCursor);
   2222                         e = element(imCursor, fi, ap);
   2223                         bmList.add(e);
   2224                     }
   2225                 }
   2226             }
   2227 
   2228             /* Enable this if post sorting and segmenting needed */
   2229             bmList.sort();
   2230             bmList.segment(ap.getMaxListCount(), offsetNum);
   2231             List<BluetoothMapMessageListingElement> list = bmList.getList();
   2232             int listSize = list.size();
   2233             Cursor tmpCursor = null;
   2234             for(int x=0;x<listSize;x++){
   2235                 BluetoothMapMessageListingElement ele = list.get(x);
   2236                 /* If OBEX "GET" request header includes "ParameterMask" with 'Type' NOT set,
   2237                  * then ele.getType() returns "null" even for a valid cursor.
   2238                  * Avoid NullPointerException in equals() check when 'mType' value is "null" */
   2239                 TYPE tmpType = ele.getType();
   2240                 if (smsCursor!= null &&
   2241                         ((TYPE.SMS_GSM).equals(tmpType) || (TYPE.SMS_CDMA).equals(tmpType))) {
   2242                     tmpCursor = smsCursor;
   2243                     fi.mMsgType = FilterInfo.TYPE_SMS;
   2244                 } else if(mmsCursor != null && (TYPE.MMS).equals(tmpType)) {
   2245                     tmpCursor = mmsCursor;
   2246                     fi.mMsgType = FilterInfo.TYPE_MMS;
   2247                 } else if(emailCursor != null && ((TYPE.EMAIL).equals(tmpType))) {
   2248                     tmpCursor = emailCursor;
   2249                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
   2250                 } else if(imCursor != null && ((TYPE.IM).equals(tmpType))) {
   2251                     tmpCursor = imCursor;
   2252                     fi.mMsgType = FilterInfo.TYPE_IM;
   2253                 }
   2254                 if(tmpCursor != null){
   2255                     tmpCursor.moveToPosition(ele.getCursorIndex());
   2256                     setSenderAddressing(ele, tmpCursor, fi, ap);
   2257                     setSenderName(ele, tmpCursor, fi, ap);
   2258                     setRecipientAddressing(ele, tmpCursor, fi, ap);
   2259                     setRecipientName(ele, tmpCursor, fi, ap);
   2260                     setSubject(ele, tmpCursor, fi, ap);
   2261                     setSize(ele, tmpCursor, fi, ap);
   2262                     setText(ele, tmpCursor, fi, ap);
   2263                     setPriority(ele, tmpCursor, fi, ap);
   2264                     setSent(ele, tmpCursor, fi, ap);
   2265                     setProtected(ele, tmpCursor, fi, ap);
   2266                     setReceptionStatus(ele, tmpCursor, fi, ap);
   2267                     setAttachment(ele, tmpCursor, fi, ap);
   2268 
   2269                     if(mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10 ){
   2270                         setDeliveryStatus(ele, tmpCursor, fi, ap);
   2271                         setThreadId(ele, tmpCursor, fi, ap);
   2272                         setThreadName(ele, tmpCursor, fi, ap);
   2273                         setFolderType(ele, tmpCursor, fi, ap);
   2274                     }
   2275                 }
   2276             }
   2277         } finally {
   2278             if(emailCursor != null)emailCursor.close();
   2279             if(smsCursor != null)smsCursor.close();
   2280             if(mmsCursor != null)mmsCursor.close();
   2281             if(imCursor != null)imCursor.close();
   2282         }
   2283 
   2284 
   2285         if(D)Log.d(TAG, "messagelisting end");
   2286         return bmList;
   2287     }
   2288 
   2289     /**
   2290      * Get the size of the message listing
   2291      * @param folder Must contain a valid folder string != null
   2292      * @param ap Parameters specifying message content and filters
   2293      * @return Integer equal to message listing size
   2294      */
   2295     public int msgListingSize(BluetoothMapFolderElement folderElement,
   2296             BluetoothMapAppParams ap) {
   2297         if (D) Log.d(TAG, "msgListingSize: folder = " + folderElement.getName());
   2298         int cnt = 0;
   2299 
   2300         /* Cache some info used throughout filtering */
   2301         FilterInfo fi = new FilterInfo();
   2302         setFilterInfo(fi);
   2303 
   2304         if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
   2305             fi.mMsgType = FilterInfo.TYPE_SMS;
   2306             String where = setWhereFilter(folderElement, fi, ap);
   2307             Cursor c = mResolver.query(Sms.CONTENT_URI,
   2308                     SMS_PROJECTION, where, null, Sms.DATE + " DESC");
   2309             try {
   2310                 if (c != null) {
   2311                     cnt = c.getCount();
   2312                 }
   2313             } finally {
   2314                 if (c != null) c.close();
   2315             }
   2316         }
   2317 
   2318         if (mmsSelected(ap)  && folderElement.hasSmsMmsContent()) {
   2319             fi.mMsgType = FilterInfo.TYPE_MMS;
   2320             String where = setWhereFilter(folderElement, fi, ap);
   2321             Cursor c = mResolver.query(Mms.CONTENT_URI,
   2322                     MMS_PROJECTION, where, null, Mms.DATE + " DESC");
   2323             try {
   2324                 if (c != null) {
   2325                     cnt += c.getCount();
   2326                 }
   2327             } finally {
   2328                 if (c != null) c.close();
   2329             }
   2330         }
   2331 
   2332         if (emailSelected(ap) && folderElement.hasEmailContent()) {
   2333             fi.mMsgType = FilterInfo.TYPE_EMAIL;
   2334             String where = setWhereFilter(folderElement, fi, ap);
   2335             if(!where.isEmpty()) {
   2336                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
   2337                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
   2338                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
   2339                 try {
   2340                     if (c != null) {
   2341                         cnt += c.getCount();
   2342                     }
   2343                 } finally {
   2344                     if (c != null) c.close();
   2345                 }
   2346             }
   2347         }
   2348 
   2349         if (imSelected(ap) && folderElement.hasImContent()) {
   2350             fi.mMsgType = FilterInfo.TYPE_IM;
   2351             String where = setWhereFilter(folderElement, fi, ap);
   2352             if(!where.isEmpty()) {
   2353                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
   2354                 Cursor c = mResolver.query(contentUri,
   2355                         BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
   2356                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
   2357                 try {
   2358                     if (c != null) {
   2359                         cnt += c.getCount();
   2360                     }
   2361                 } finally {
   2362                     if (c != null) c.close();
   2363                 }
   2364             }
   2365         }
   2366 
   2367         if (D) Log.d(TAG, "msgListingSize: size = " + cnt);
   2368         return cnt;
   2369     }
   2370 
   2371     /**
   2372      * Return true if there are unread messages in the requested list of messages
   2373      * @param folder folder where the message listing should come from
   2374      * @param ap application parameter object
   2375      * @return true if unread messages are in the list, else false
   2376      */
   2377     public boolean msgListingHasUnread(BluetoothMapFolderElement folderElement,
   2378             BluetoothMapAppParams ap) {
   2379         if (D) Log.d(TAG, "msgListingHasUnread: folder = " + folderElement.getName());
   2380         int cnt = 0;
   2381 
   2382         /* Cache some info used throughout filtering */
   2383         FilterInfo fi = new FilterInfo();
   2384         setFilterInfo(fi);
   2385 
   2386        if (smsSelected(fi, ap)  && folderElement.hasSmsMmsContent()) {
   2387             fi.mMsgType = FilterInfo.TYPE_SMS;
   2388             String where = setWhereFilterFolderType(folderElement, fi);
   2389             where += " AND " + Sms.READ + "=0 ";
   2390             where += setWhereFilterPeriod(ap, fi);
   2391             Cursor c = mResolver.query(Sms.CONTENT_URI,
   2392                 SMS_PROJECTION, where, null, Sms.DATE + " DESC");
   2393             try {
   2394                 if (c != null) {
   2395                     cnt = c.getCount();
   2396                 }
   2397             } finally {
   2398                 if (c != null) c.close();
   2399             }
   2400         }
   2401 
   2402         if (mmsSelected(ap)  && folderElement.hasSmsMmsContent()) {
   2403             fi.mMsgType = FilterInfo.TYPE_MMS;
   2404             String where = setWhereFilterFolderType(folderElement, fi);
   2405             where += " AND " + Mms.READ + "=0 ";
   2406             where += setWhereFilterPeriod(ap, fi);
   2407             Cursor c = mResolver.query(Mms.CONTENT_URI,
   2408                 MMS_PROJECTION, where, null, Sms.DATE + " DESC");
   2409             try {
   2410                 if (c != null) {
   2411                     cnt += c.getCount();
   2412                 }
   2413             } finally {
   2414                 if (c != null) c.close();
   2415             }
   2416         }
   2417 
   2418 
   2419         if (emailSelected(ap) && folderElement.getFolderId() != -1) {
   2420             fi.mMsgType = FilterInfo.TYPE_EMAIL;
   2421             String where = setWhereFilterFolderType(folderElement, fi);
   2422             if(!where.isEmpty()) {
   2423                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
   2424                 where += setWhereFilterPeriod(ap, fi);
   2425                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
   2426                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
   2427                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
   2428                 try {
   2429                     if (c != null) {
   2430                         cnt += c.getCount();
   2431                     }
   2432                 } finally {
   2433                     if (c != null) c.close();
   2434                 }
   2435             }
   2436         }
   2437 
   2438         if (imSelected(ap) && folderElement.hasImContent()) {
   2439             fi.mMsgType = FilterInfo.TYPE_IM;
   2440             String where = setWhereFilter(folderElement, fi, ap);
   2441             if(!where.isEmpty()) {
   2442                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
   2443                 where += setWhereFilterPeriod(ap, fi);
   2444                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
   2445                 Cursor c = mResolver.query(contentUri,
   2446                         BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
   2447                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
   2448                 try {
   2449                     if (c != null) {
   2450                         cnt += c.getCount();
   2451                     }
   2452                 } finally {
   2453                     if (c != null) c.close();
   2454                 }
   2455             }
   2456         }
   2457 
   2458         if (D) Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt);
   2459         return (cnt>0)?true:false;
   2460     }
   2461 
   2462     /**
   2463      * Build the conversation listing.
   2464      * @param ap The Application Parameters
   2465      * @param sizeOnly TRUE: don't populate the list members, only build the list to get the size.
   2466      * @return
   2467      */
   2468     public BluetoothMapConvoListing convoListing(BluetoothMapAppParams ap, boolean sizeOnly) {
   2469 
   2470         if (D) Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType() );
   2471         BluetoothMapConvoListing convoList = new BluetoothMapConvoListing();
   2472 
   2473         /* We overwrite the parameter mask here if it is 0 or not present, as this
   2474          * should cause all parameters to be included in the message list. */
   2475         if(ap.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
   2476                 ap.getConvoParameterMask() == 0) {
   2477             ap.setConvoParameterMask(CONVO_PARAMETER_MASK_DEFAULT);
   2478             if (D) Log.v(TAG, "convoListing(): appParameterMask is zero or not present, " +
   2479                     "changing to default: " + ap.getConvoParameterMask());
   2480         }
   2481 
   2482         /* Possible filters:
   2483          *  - Recipient name (contacts DB) or id (for SMS/MMS this is the thread-id contact-id)
   2484          *  - Activity start/begin
   2485          *  - Read status
   2486          *  - Thread_id
   2487          * The strategy for SMS/MMS
   2488          *   With no filter on name - use limit and offset.
   2489          *   With a filter on name - build the complete list of conversations and create a filter
   2490          *                           mechanism
   2491          *
   2492          * The strategy for IM:
   2493          *   Join the conversation table with the contacts table in a way that makes it possible to
   2494          *   get the data needed in a single query.
   2495          *   Manually handle limit/offset
   2496          * */
   2497 
   2498         /* Cache some info used throughout filtering */
   2499         FilterInfo fi = new FilterInfo();
   2500         setFilterInfo(fi);
   2501         Cursor smsMmsCursor = null;
   2502         Cursor imEmailCursor = null;
   2503         int offsetNum;
   2504         if(sizeOnly) {
   2505             offsetNum = 0;
   2506         } else {
   2507             offsetNum = ap.getStartOffset();
   2508         }
   2509         // Inverse meaning - hence a 1 is include.
   2510         int msgTypesInclude = ((~ap.getFilterMessageType())
   2511                 & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK);
   2512         int maxThreads = ap.getMaxListCount()+ap.getStartOffset();
   2513 
   2514 
   2515         try {
   2516             if (smsSelected(fi, ap) || mmsSelected(ap)) {
   2517                 String limit = "";
   2518                 if((sizeOnly == false) && (ap.getMaxListCount()>0) &&
   2519                         (ap.getFilterRecipient()==null)){
   2520                     /* We can only use limit if we do not have a contacts filter */
   2521                     limit=" LIMIT " + maxThreads;
   2522                 }
   2523                 StringBuilder sortOrder = new StringBuilder(Threads.DATE + " DESC");
   2524                 if((sizeOnly == false) &&
   2525                         ((msgTypesInclude & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM |
   2526                         BluetoothMapAppParams.FILTER_NO_SMS_CDMA) |
   2527                         BluetoothMapAppParams.FILTER_NO_MMS) == 0)
   2528                         && ap.getFilterRecipient() == null){
   2529                     // SMS/MMS messages only and no recipient filter - use optimization.
   2530                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
   2531                     if(D) Log.d(TAG, "SMS Limit => "+limit);
   2532                     offsetNum = 0;
   2533                 }
   2534                 StringBuilder selection = new StringBuilder(120); // This covers most cases
   2535                 ArrayList<String> selectionArgs = new ArrayList<String>(12); // Covers all cases
   2536                 selection.append("1=1 "); // just to simplify building the where-clause
   2537                 setConvoWhereFilterSmsMms(selection, selectionArgs, fi, ap);
   2538                 String[] args = null;
   2539                 if(selectionArgs.size() > 0) {
   2540                     args = new String[selectionArgs.size()];
   2541                     selectionArgs.toArray(args);
   2542                 }
   2543                 Uri uri = Threads.CONTENT_URI.buildUpon()
   2544                         .appendQueryParameter("simple", "true").build();
   2545                 sortOrder.append(limit);
   2546                 if(D) Log.d(TAG, "Query using selection: " + selection.toString() +
   2547                         " - sortOrder: " + sortOrder.toString());
   2548                 // TODO: Optimize: Reduce projection based on convo parameter mask
   2549                 smsMmsCursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(),
   2550                         args, sortOrder.toString());
   2551                 if (smsMmsCursor != null) {
   2552                     // store column index so we don't have to look them up anymore (optimization)
   2553                     if(D) Log.d(TAG, "Found " + smsMmsCursor.getCount()
   2554                             + " sms/mms conversations.");
   2555                     BluetoothMapConvoListingElement convoElement = null;
   2556                     smsMmsCursor.moveToPosition(-1);
   2557                     if(ap.getFilterRecipient() == null) {
   2558                         int count = 0;
   2559                         // We have no Recipient filter, add contacts after the list is reduced
   2560                         while (smsMmsCursor.moveToNext()) {
   2561                             convoElement = createConvoElement(smsMmsCursor, fi, ap);
   2562                             convoList.add(convoElement);
   2563                             count++;
   2564                             if(sizeOnly == false && count >= maxThreads) {
   2565                                 break;
   2566                             }
   2567                         }
   2568                     } else {
   2569                         // We must be able to filter on recipient, add contacts now
   2570                         SmsMmsContacts contacts = new SmsMmsContacts();
   2571                         while (smsMmsCursor.moveToNext()) {
   2572                             int count = 0;
   2573                             convoElement = createConvoElement(smsMmsCursor, fi, ap);
   2574                             String idsStr =
   2575                                     smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
   2576                             // Add elements only if we do find a contact - if not we cannot apply
   2577                             // the filter, hence the item is irrelevant
   2578                             // TODO: Perhaps the spec. should be changes to be able to search on
   2579                             //       phone number as well?
   2580                             if(addSmsMmsContacts(convoElement, contacts, idsStr,
   2581                                     ap.getFilterRecipient(), ap)) {
   2582                                 convoList.add(convoElement);
   2583                                 if(sizeOnly == false && count >= maxThreads) {
   2584                                     break;
   2585                                 }
   2586                             }
   2587                         }
   2588                     }
   2589                 }
   2590             }
   2591 
   2592             if (emailSelected(ap) || imSelected(ap)) {
   2593                 int count = 0;
   2594                 if(emailSelected(ap)) {
   2595                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
   2596                 } else if(imSelected(ap)) {
   2597                     fi.mMsgType = FilterInfo.TYPE_IM;
   2598                 }
   2599                 if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
   2600                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
   2601 
   2602                 contentUri = appendConvoListQueryParameters(ap, contentUri);
   2603                 if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
   2604                 // TODO: Optimize: Reduce projection based on convo parameter mask
   2605                 imEmailCursor = mResolver.query(contentUri,
   2606                         BluetoothMapContract.BT_CONVERSATION_PROJECTION,
   2607                         null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
   2608                         + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
   2609                         + " ASC");
   2610                 if (imEmailCursor != null) {
   2611                     BluetoothMapConvoListingElement e = null;
   2612                     // store column index so we don't have to look them up anymore (optimization)
   2613                     // Here we rely on only a single account-based message type for each MAS.
   2614                     fi.setEmailImConvoColumns(imEmailCursor);
   2615                     boolean isValid = imEmailCursor.moveToNext();
   2616                     if(D) Log.d(TAG, "Found " + imEmailCursor.getCount()
   2617                             + " EMAIL/IM conversations. isValid = " + isValid);
   2618                     while (isValid && ((sizeOnly == true) || (count < maxThreads))) {
   2619                         long threadId = imEmailCursor.getLong(fi.mConvoColConvoId);
   2620                         long nextThreadId;
   2621                         count ++;
   2622                         e = createConvoElement(imEmailCursor, fi, ap);
   2623                         convoList.add(e);
   2624 
   2625                         do {
   2626                             nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
   2627                             if(V) Log.i(TAG, "  threadId = " + threadId + " newThreadId = " +
   2628                                     nextThreadId);
   2629                             // TODO: This seems rather inefficient in the case where we do not need
   2630                             //       to reduce the list.
   2631                         } while ((nextThreadId == threadId) &&
   2632                                 (isValid = imEmailCursor.moveToNext() == true));
   2633                     }
   2634                 }
   2635             }
   2636 
   2637             if(D) Log.d(TAG, "Done adding conversations - list size:" +
   2638                     convoList.getCount());
   2639 
   2640             // If sizeOnly - we are all done here - return the list as is - no need to populate the
   2641             // list.
   2642             if(sizeOnly) {
   2643                 return convoList;
   2644             }
   2645 
   2646             /* Enable this if post sorting and segmenting needed */
   2647             /* This is too early */
   2648             convoList.sort();
   2649             convoList.segment(ap.getMaxListCount(), offsetNum);
   2650             List<BluetoothMapConvoListingElement> list = convoList.getList();
   2651             int listSize = list.size();
   2652             if(V) Log.i(TAG, "List Size:" + listSize);
   2653             Cursor tmpCursor = null;
   2654             SmsMmsContacts contacts = new SmsMmsContacts();
   2655             for(int x=0;x<listSize;x++){
   2656                 BluetoothMapConvoListingElement ele = list.get(x);
   2657                 TYPE type = ele.getType();
   2658                 switch(type) {
   2659                 case SMS_CDMA:
   2660                 case SMS_GSM:
   2661                 case MMS: {
   2662                     tmpCursor = null; // SMS/MMS needs special treatment
   2663                     if(smsMmsCursor != null) {
   2664                         populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts);
   2665                     }
   2666                     if(D) fi.mMsgType = FilterInfo.TYPE_IM;
   2667                     break;
   2668                 }
   2669                 case EMAIL:
   2670                     tmpCursor = imEmailCursor;
   2671                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
   2672                     break;
   2673                 case IM:
   2674                     tmpCursor = imEmailCursor;
   2675                     fi.mMsgType = FilterInfo.TYPE_IM;
   2676                     break;
   2677                 default:
   2678                     tmpCursor = null;
   2679                     break;
   2680                 }
   2681 
   2682                 if(D) Log.d(TAG, "Working on cursor of type " + fi.mMsgType);
   2683 
   2684                 if(tmpCursor != null){
   2685                     populateImEmailConvoElement(ele, tmpCursor, ap, fi);
   2686                 }else {
   2687                     // No, it will be for SMS/MMS at the moment
   2688                     if(D) Log.d(TAG, "tmpCursor is Null - something is wrong - or the message is" +
   2689                             " of type SMS/MMS");
   2690                 }
   2691             }
   2692         } finally {
   2693             if(imEmailCursor != null)imEmailCursor.close();
   2694             if(smsMmsCursor != null)smsMmsCursor.close();
   2695             if(D)Log.d(TAG, "conversation end");
   2696         }
   2697         return convoList;
   2698     }
   2699 
   2700 
   2701     /**
   2702      * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a
   2703      * new ConvoListVersinoCounter in mSmsMmsConvoListVersion
   2704      * @return
   2705      */
   2706     /* package */
   2707     boolean refreshSmsMmsConvoVersions() {
   2708         boolean listChangeDetected = false;
   2709         Cursor cursor = null;
   2710         Uri uri = Threads.CONTENT_URI.buildUpon()
   2711                 .appendQueryParameter("simple", "true").build();
   2712         cursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null,
   2713                 null, Threads.DATE + " DESC");
   2714         try {
   2715             if (cursor != null) {
   2716                 // store column index so we don't have to look them up anymore (optimization)
   2717                 if(D) Log.d(TAG, "Found " + cursor.getCount()
   2718                         + " sms/mms conversations.");
   2719                 BluetoothMapConvoListingElement convoElement = null;
   2720                 cursor.moveToPosition(-1);
   2721                 synchronized (getSmsMmsConvoList()) {
   2722                     int size = Math.max(getSmsMmsConvoList().size(), cursor.getCount());
   2723                     HashMap<Long,BluetoothMapConvoListingElement> newList =
   2724                             new HashMap<Long,BluetoothMapConvoListingElement>(size);
   2725                     while (cursor.moveToNext()) {
   2726                         // TODO: Extract to function, that can be called at listing, which returns
   2727                         //       the versionCounter(existing or new).
   2728                         boolean convoChanged = false;
   2729                         Long id = cursor.getLong(MMS_SMS_THREAD_COL_ID);
   2730                         convoElement = getSmsMmsConvoList().remove(id);
   2731                         if(convoElement == null) {
   2732                             // New conversation added
   2733                             convoElement = new BluetoothMapConvoListingElement();
   2734                             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
   2735                             listChangeDetected = true;
   2736                             convoElement.setVersionCounter(0);
   2737                         }
   2738                         // Currently we only need to compare name, last_activity and read_status, and
   2739                         // name is not used for SMS/MMS.
   2740                         // msg delete will be handled by update folderVersionCounter().
   2741                         long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
   2742                         boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
   2743                                 true : false;
   2744 
   2745                         if(last_activity != convoElement.getLastActivity()) {
   2746                             convoChanged = true;
   2747                             convoElement.setLastActivity(last_activity);
   2748                         }
   2749 
   2750                         if(read != convoElement.getReadBool()) {
   2751                             convoChanged = true;
   2752                             convoElement.setRead(read, false);
   2753                         }
   2754 
   2755                         String idsStr = cursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
   2756                         if(!idsStr.equals(convoElement.getSmsMmsContacts())) {
   2757                             // This should not trigger a change in conversationVersionCounter only the
   2758                             // ConvoListVersionCounter.
   2759                             listChangeDetected = true;
   2760                             convoElement.setSmsMmsContacts(idsStr);
   2761                         }
   2762 
   2763                         if(convoChanged) {
   2764                             listChangeDetected = true;
   2765                             convoElement.incrementVersionCounter();
   2766                         }
   2767                         newList.put(id, convoElement);
   2768                     }
   2769                     // If we still have items on the old list, something was deleted
   2770                     if(getSmsMmsConvoList().size() != 0) {
   2771                         listChangeDetected = true;
   2772                     }
   2773                     setSmsMmsConvoList(newList);
   2774                 }
   2775 
   2776                 if(listChangeDetected) {
   2777                     mMasInstance.updateSmsMmsConvoListVersionCounter();
   2778                 }
   2779             }
   2780         } finally {
   2781             if(cursor != null) {
   2782                 cursor.close();
   2783             }
   2784         }
   2785         return listChangeDetected;
   2786     }
   2787 
   2788     /**
   2789      * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a
   2790      * new ConvoListVersinoCounter in mSmsMmsConvoListVersion
   2791      * @return
   2792      */
   2793     /* package */
   2794     boolean refreshImEmailConvoVersions() {
   2795         boolean listChangeDetected = false;
   2796         FilterInfo fi = new FilterInfo();
   2797 
   2798         Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
   2799 
   2800         if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
   2801         Cursor imEmailCursor = mResolver.query(contentUri,
   2802                 CONVO_VERSION_PROJECTION,
   2803                 null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
   2804                 + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
   2805                 + " ASC");
   2806         try {
   2807             if (imEmailCursor != null) {
   2808                 BluetoothMapConvoListingElement convoElement = null;
   2809                 // store column index so we don't have to look them up anymore (optimization)
   2810                 // Here we rely on only a single account-based message type for each MAS.
   2811                 fi.setEmailImConvoColumns(imEmailCursor);
   2812                 boolean isValid = imEmailCursor.moveToNext();
   2813                 if(V) Log.d(TAG, "Found " + imEmailCursor.getCount()
   2814                         + " EMAIL/IM conversations. isValid = " + isValid);
   2815                 synchronized (getImEmailConvoList()) {
   2816                     int size = Math.max(getImEmailConvoList().size(), imEmailCursor.getCount());
   2817                     boolean convoChanged = false;
   2818                     HashMap<Long,BluetoothMapConvoListingElement> newList =
   2819                             new HashMap<Long,BluetoothMapConvoListingElement>(size);
   2820                     while (isValid) {
   2821                         long id = imEmailCursor.getLong(fi.mConvoColConvoId);
   2822                         long nextThreadId;
   2823                         convoElement = getImEmailConvoList().remove(id);
   2824                         if(convoElement == null) {
   2825                             // New conversation added
   2826                             convoElement = new BluetoothMapConvoListingElement();
   2827                             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
   2828                             listChangeDetected = true;
   2829                             convoElement.setVersionCounter(0);
   2830                         }
   2831                         String name = imEmailCursor.getString(fi.mConvoColName);
   2832                         String summary = imEmailCursor.getString(fi.mConvoColSummary);
   2833                         long last_activity = imEmailCursor.getLong(fi.mConvoColLastActivity);
   2834                         boolean read = (imEmailCursor.getInt(fi.mConvoColRead) == 1) ?
   2835                                 true : false;
   2836 
   2837                         if(last_activity != convoElement.getLastActivity()) {
   2838                             convoChanged = true;
   2839                             convoElement.setLastActivity(last_activity);
   2840                         }
   2841 
   2842                         if(read != convoElement.getReadBool()) {
   2843                             convoChanged = true;
   2844                             convoElement.setRead(read, false);
   2845                         }
   2846 
   2847                         if(name != null && !name.equals(convoElement.getName())) {
   2848                             convoChanged = true;
   2849                             convoElement.setName(name);
   2850                         }
   2851 
   2852                         if(summary != null && !summary.equals(convoElement.getFullSummary())) {
   2853                             convoChanged = true;
   2854                             convoElement.setSummary(summary);
   2855                         }
   2856                         /* If the query returned one row for each contact, skip all the dublicates */
   2857                         do {
   2858                             nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
   2859                             if(V) Log.i(TAG, "  threadId = " + id + " newThreadId = " +
   2860                                     nextThreadId);
   2861                         } while ((nextThreadId == id) &&
   2862                                 (isValid = imEmailCursor.moveToNext() == true));
   2863 
   2864                         if(convoChanged) {
   2865                             listChangeDetected = true;
   2866                             convoElement.incrementVersionCounter();
   2867                         }
   2868                         newList.put(id, convoElement);
   2869                     }
   2870                     // If we still have items on the old list, something was deleted
   2871                     if(getImEmailConvoList().size() != 0) {
   2872                         listChangeDetected = true;
   2873                     }
   2874                     setImEmailConvoList(newList);
   2875                 }
   2876             }
   2877         } finally {
   2878             if(imEmailCursor != null) {
   2879                 imEmailCursor.close();
   2880             }
   2881         }
   2882 
   2883         if(listChangeDetected) {
   2884             mMasInstance.updateImEmailConvoListVersionCounter();
   2885         }
   2886         return listChangeDetected;
   2887     }
   2888 
   2889     /**
   2890      * Update the convoVersionCounter within the element passed as parameter.
   2891      * This function has the side effect to update the ConvoListVersionCounter if needed.
   2892      * This function ignores changes to contacts as this shall not change the convoVersionCounter,
   2893      * only the convoListVersion counter, which will be updated upon request.
   2894      * @param ele Element to update shall not be null.
   2895      */
   2896     private void updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele) {
   2897         long id = ele.getCpConvoId();
   2898         BluetoothMapConvoListingElement convoElement = getSmsMmsConvoList().get(id);
   2899         boolean listChangeDetected = false;
   2900         boolean convoChanged = false;
   2901         if(convoElement == null) {
   2902             // New conversation added
   2903             convoElement = new BluetoothMapConvoListingElement();
   2904             getSmsMmsConvoList().put(id, convoElement);
   2905             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
   2906             listChangeDetected = true;
   2907             convoElement.setVersionCounter(0);
   2908         }
   2909         long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
   2910         boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
   2911                 true : false;
   2912 
   2913         if(last_activity != convoElement.getLastActivity()) {
   2914             convoChanged = true;
   2915             convoElement.setLastActivity(last_activity);
   2916         }
   2917 
   2918         if(read != convoElement.getReadBool()) {
   2919             convoChanged = true;
   2920             convoElement.setRead(read, false);
   2921         }
   2922 
   2923         if(convoChanged) {
   2924             listChangeDetected = true;
   2925             convoElement.incrementVersionCounter();
   2926         }
   2927         if(listChangeDetected) {
   2928             mMasInstance.updateSmsMmsConvoListVersionCounter();
   2929         }
   2930         ele.setVersionCounter(convoElement.getVersionCounter());
   2931     }
   2932 
   2933     /**
   2934      * Update the convoVersionCounter within the element passed as parameter.
   2935      * This function has the side effect to update the ConvoListVersionCounter if needed.
   2936      * This function ignores changes to contacts as this shall not change the convoVersionCounter,
   2937      * only the convoListVersion counter, which will be updated upon request.
   2938      * @param ele Element to update shall not be null.
   2939      */
   2940     private void updateImEmailConvoVersion(Cursor cursor, FilterInfo fi,
   2941             BluetoothMapConvoListingElement ele) {
   2942         long id = ele.getCpConvoId();
   2943         BluetoothMapConvoListingElement convoElement = getImEmailConvoList().get(id);
   2944         boolean listChangeDetected = false;
   2945         boolean convoChanged = false;
   2946         if(convoElement == null) {
   2947             // New conversation added
   2948             if(V) Log.d(TAG, "Added new conversation with ID = " + id);
   2949             convoElement = new BluetoothMapConvoListingElement();
   2950             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
   2951             getImEmailConvoList().put(id, convoElement);
   2952             listChangeDetected = true;
   2953             convoElement.setVersionCounter(0);
   2954         }
   2955         String name = cursor.getString(fi.mConvoColName);
   2956         long last_activity = cursor.getLong(fi.mConvoColLastActivity);
   2957         boolean read = (cursor.getInt(fi.mConvoColRead) == 1) ?
   2958                 true : false;
   2959 
   2960         if(last_activity != convoElement.getLastActivity()) {
   2961             convoChanged = true;
   2962             convoElement.setLastActivity(last_activity);
   2963         }
   2964 
   2965         if(read != convoElement.getReadBool()) {
   2966             convoChanged = true;
   2967             convoElement.setRead(read, false);
   2968         }
   2969 
   2970         if(name != null && !name.equals(convoElement.getName())) {
   2971             convoChanged = true;
   2972             convoElement.setName(name);
   2973         }
   2974 
   2975         if(convoChanged) {
   2976             listChangeDetected = true;
   2977             if(V) Log.d(TAG, "conversation with ID = " + id + " changed");
   2978             convoElement.incrementVersionCounter();
   2979         }
   2980         if(listChangeDetected) {
   2981             mMasInstance.updateImEmailConvoListVersionCounter();
   2982         }
   2983         ele.setVersionCounter(convoElement.getVersionCounter());
   2984     }
   2985 
   2986     /**
   2987      * @param ele
   2988      * @param smsMmsCursor
   2989      * @param ap
   2990      * @param contacts
   2991      */
   2992     private void populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele,
   2993             Cursor smsMmsCursor, BluetoothMapAppParams ap,
   2994             SmsMmsContacts contacts) {
   2995         smsMmsCursor.moveToPosition(ele.getCursorIndex());
   2996         // TODO: If we ever get beyond 31 bit, change to long
   2997         int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
   2998 
   2999         // TODO: How to determine whether the convo-IDs can be used across message
   3000         //       types?
   3001         ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS,
   3002                 smsMmsCursor.getLong(MMS_SMS_THREAD_COL_ID));
   3003 
   3004         boolean read = (smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
   3005                 true : false;
   3006         if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
   3007             ele.setRead(read, true);
   3008         } else {
   3009             ele.setRead(read, false);
   3010         }
   3011 
   3012         if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
   3013             long timeStamp = smsMmsCursor.getLong(MMS_SMS_THREAD_COL_DATE);
   3014             ele.setLastActivity(timeStamp);
   3015         } else {
   3016             // We need to delete the time stamp, if it was added for multi msg-type
   3017             ele.setLastActivity(-1);
   3018         }
   3019 
   3020         if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
   3021             updateSmsMmsConvoVersion(smsMmsCursor, ele);
   3022         }
   3023 
   3024         if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
   3025             ele.setName(""); // We never have a thread name for SMS/MMS
   3026         }
   3027 
   3028         if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
   3029             String summary = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET);
   3030             String cs = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET_CS);
   3031             if(summary != null && cs != null && !cs.equals("UTF-8")) {
   3032                 try {
   3033                     // TODO: Not sure this is how to convert to UTF-8
   3034                     summary = new String(summary.getBytes(cs),"UTF-8");
   3035                 } catch (UnsupportedEncodingException e){/*Cannot happen*/}
   3036             }
   3037             ele.setSummary(summary);
   3038         }
   3039 
   3040         if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
   3041             if(ap.getFilterRecipient() == null) {
   3042                 // Add contacts only if not already added
   3043                 String idsStr =
   3044                         smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
   3045                 addSmsMmsContacts(ele, contacts, idsStr, null, ap);
   3046             }
   3047         }
   3048     }
   3049 
   3050     /**
   3051      * @param ele
   3052      * @param tmpCursor
   3053      * @param fi
   3054      */
   3055     private void populateImEmailConvoElement( BluetoothMapConvoListingElement ele,
   3056             Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi) {
   3057         tmpCursor.moveToPosition(ele.getCursorIndex());
   3058         // TODO: If we ever get beyond 31 bit, change to long
   3059         int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
   3060         long threadId = tmpCursor.getLong(fi.mConvoColConvoId);
   3061 
   3062         // Mandatory field
   3063         ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, threadId);
   3064 
   3065         if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
   3066             ele.setName(tmpCursor.getString(fi.mConvoColName));
   3067         }
   3068 
   3069         boolean reportRead = false;
   3070         if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
   3071             reportRead = true;
   3072         }
   3073         ele.setRead(((1==tmpCursor.getInt(fi.mConvoColRead))?true:false), reportRead);
   3074 
   3075         long timestamp = tmpCursor.getLong(fi.mConvoColLastActivity);
   3076         if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
   3077             ele.setLastActivity(timestamp);
   3078         } else {
   3079             // We need to delete the time stamp, if it was added for multi msg-type
   3080             ele.setLastActivity(-1);
   3081         }
   3082 
   3083 
   3084         if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
   3085             updateImEmailConvoVersion(tmpCursor, fi, ele);
   3086         }
   3087         if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
   3088             ele.setSummary(tmpCursor.getString(fi.mConvoColSummary));
   3089         }
   3090         // TODO: For optimization, we could avoid joining the contact and convo tables
   3091         //       if we have no filter nor this bit is set.
   3092         if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
   3093             do {
   3094                 BluetoothMapConvoContactElement c = new BluetoothMapConvoContactElement();
   3095                 if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) {
   3096                     c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid),0));
   3097                 }
   3098                 if((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) {
   3099                     c.setChatState(tmpCursor.getInt(fi.mContactColChatState));
   3100                 }
   3101                 if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) {
   3102                     c.setPresenceAvailability(tmpCursor.getInt(fi.mContactColPresenceState));
   3103                 }
   3104                 if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) {
   3105                     c.setPresenceStatus(tmpCursor.getString(fi.mContactColPresenceText));
   3106                 }
   3107                 if((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) {
   3108                     c.setPriority(tmpCursor.getInt(fi.mContactColPriority));
   3109                 }
   3110                 if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) {
   3111                     c.setDisplayName(tmpCursor.getString(fi.mContactColNickname));
   3112                 }
   3113                 if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
   3114                     c.setContactId(tmpCursor.getString(fi.mContactColContactUci));
   3115                 }
   3116                 if((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) {
   3117                     c.setLastActivity(tmpCursor.getLong(fi.mContactColLastActive));
   3118                 }
   3119                 if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
   3120                     c.setName(tmpCursor.getString(fi.mContactColName));
   3121                 }
   3122                 ele.addContact(c);
   3123             } while (tmpCursor.moveToNext() == true
   3124                     && tmpCursor.getLong(fi.mConvoColConvoId) == threadId);
   3125         }
   3126     }
   3127 
   3128     /**
   3129      * Extract the ConvoList parameters from appParams and build the matching URI with
   3130      * query parameters.
   3131      * @param ap the appParams from the request
   3132      * @param contentUri the URI to append parameters to
   3133      * @return the new URI with the appended parameters (if any)
   3134      */
   3135     private Uri appendConvoListQueryParameters(BluetoothMapAppParams ap,
   3136             Uri contentUri) {
   3137         Builder newUri = contentUri.buildUpon();
   3138         String str = ap.getFilterRecipient();
   3139         if(str != null) {
   3140             str = str.trim();
   3141             str = str.replace("*", "%");
   3142             newUri.appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, str);
   3143         }
   3144         long time = ap.getFilterLastActivityBegin();
   3145         if(time > 0) {
   3146             newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN,
   3147                     Long.toString(time));
   3148         }
   3149         time = ap.getFilterLastActivityEnd();
   3150         if(time > 0) {
   3151             newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_END,
   3152                     Long.toString(time));
   3153         }
   3154         int readStatus = ap.getFilterReadStatus();
   3155         if(readStatus > 0) {
   3156             if(readStatus == 1) {
   3157                 // Conversations with Unread messages only
   3158                 newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
   3159                         "false");
   3160             }else if(readStatus == 2) {
   3161                 // Conversations with all read messages only
   3162                 newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
   3163                         "true");
   3164             }
   3165             // if both are set it will be the same as requesting an empty list, but
   3166             // as it makes no sense with such a structure in a bit mask, we treat
   3167             // requesting both the same as no filtering.
   3168         }
   3169         long convoId = -1;
   3170         if(ap.getFilterConvoId() != null) {
   3171             convoId = ap.getFilterConvoId().getLeastSignificantBits();
   3172         }
   3173         if(convoId > 0) {
   3174             newUri.appendQueryParameter(BluetoothMapContract.FILTER_THREAD_ID,
   3175                     Long.toString(convoId));
   3176         }
   3177         return newUri.build();
   3178     }
   3179 
   3180     /**
   3181      * Procedure if we have a filter:
   3182      *  - loop through all ids to examine if there is a match (this will build the cache)
   3183      *  - If there is a match loop again to add all contacts.
   3184      *
   3185      * Procedure if we don't have a filter
   3186      *  - Add all contacts
   3187      *
   3188      * @param convoElement
   3189      * @param contacts
   3190      * @param idsStr
   3191      * @param recipientFilter
   3192      * @return
   3193      */
   3194     private boolean addSmsMmsContacts( BluetoothMapConvoListingElement convoElement,
   3195             SmsMmsContacts contacts, String idsStr, String recipientFilter,
   3196             BluetoothMapAppParams ap) {
   3197         BluetoothMapConvoContactElement contactElement;
   3198         int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
   3199         boolean foundContact = false;
   3200         String[] ids = idsStr.split(" ");
   3201         long[] longIds = new long[ids.length];
   3202         if(recipientFilter != null) {
   3203             recipientFilter = recipientFilter.trim();
   3204         }
   3205 
   3206         for (int i = 0; i < ids.length; i++) {
   3207             long longId;
   3208             try {
   3209                 longId = Long.parseLong(ids[i]);
   3210                 longIds[i] = longId;
   3211                 if(recipientFilter == null) {
   3212                     // If there is not filter, all we need to do is to parse the ids
   3213                     foundContact = true;
   3214                     continue;
   3215                 }
   3216                 String addr = contacts.getPhoneNumber(mResolver, longId);
   3217                 if(addr == null) {
   3218                     // This can only happen if all messages from a contact is deleted while
   3219                     // performing the query.
   3220                     continue;
   3221                 }
   3222                 MapContact contact =
   3223                         contacts.getContactNameFromPhone(addr, mResolver, recipientFilter);
   3224                 if(D) {
   3225                     Log.d(TAG, "  id " + longId + ": " + addr);
   3226                     if(contact != null) {
   3227                         Log.d(TAG,"  contact name: " + contact.getName() + "  X-BT-UID: "
   3228                                 + contact.getXBtUid());
   3229                     }
   3230                 }
   3231                 if(contact == null) {
   3232                     continue;
   3233                 }
   3234                 foundContact = true;
   3235             } catch (NumberFormatException ex) {
   3236                 // skip this id
   3237                 continue;
   3238             }
   3239         }
   3240 
   3241         if(foundContact == true) {
   3242             foundContact = false;
   3243             for (long id : longIds) {
   3244                 String addr = contacts.getPhoneNumber(mResolver, id);
   3245                 if(addr == null) {
   3246                     // This can only happen if all messages from a contact is deleted while
   3247                     // performing the query.
   3248                     continue;
   3249                 }
   3250                 foundContact = true;
   3251                 MapContact contact = contacts.getContactNameFromPhone(addr, mResolver);
   3252 
   3253                 if(contact == null) {
   3254                     // We do not have a contact, we need to manually add one
   3255                     contactElement = new BluetoothMapConvoContactElement();
   3256                     if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
   3257                         contactElement.setName(addr); // Use the phone number as name
   3258                     }
   3259                     if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
   3260                         contactElement.setContactId(addr);
   3261                     }
   3262                 } else {
   3263                     contactElement = BluetoothMapConvoContactElement
   3264                             .createFromMapContact(contact, addr);
   3265                     // Remove the parameters not to be reported
   3266                     if((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) {
   3267                         contactElement.setContactId(null);
   3268                     }
   3269                     if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) {
   3270                         contactElement.setBtUid(null);
   3271                     }
   3272                     if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) {
   3273                         contactElement.setDisplayName(null);
   3274                     }
   3275                 }
   3276                 convoElement.addContact(contactElement);
   3277             }
   3278         }
   3279         return foundContact;
   3280     }
   3281 
   3282     /**
   3283      * Get the folder name of an SMS message or MMS message.
   3284      * @param c the cursor pointing at the message
   3285      * @return the folder name.
   3286      */
   3287     private String getFolderName(int type, int threadId) {
   3288 
   3289         if(threadId == -1)
   3290             return BluetoothMapContract.FOLDER_NAME_DELETED;
   3291 
   3292         switch(type) {
   3293         case 1:
   3294             return BluetoothMapContract.FOLDER_NAME_INBOX;
   3295         case 2:
   3296             return BluetoothMapContract.FOLDER_NAME_SENT;
   3297         case 3:
   3298             return BluetoothMapContract.FOLDER_NAME_DRAFT;
   3299         case 4: // Just name outbox, failed and queued "outbox"
   3300         case 5:
   3301         case 6:
   3302             return BluetoothMapContract.FOLDER_NAME_OUTBOX;
   3303         }
   3304         return "";
   3305     }
   3306 
   3307     public byte[] getMessage(String handle, BluetoothMapAppParams appParams,
   3308             BluetoothMapFolderElement folderElement, String version)
   3309             throws UnsupportedEncodingException{
   3310         TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
   3311         mMessageVersion = version;
   3312         long id = BluetoothMapUtils.getCpHandle(handle);
   3313         if(appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) {
   3314             throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as" +
   3315                                                " we always return the full message.");
   3316         }
   3317         switch(type) {
   3318         case SMS_GSM:
   3319         case SMS_CDMA:
   3320             return getSmsMessage(id, appParams.getCharset());
   3321         case MMS:
   3322             return getMmsMessage(id, appParams);
   3323         case EMAIL:
   3324             return getEmailMessage(id, appParams, folderElement);
   3325         case IM:
   3326             return getIMMessage(id, appParams, folderElement);
   3327         }
   3328         throw new IllegalArgumentException("Invalid message handle.");
   3329     }
   3330 
   3331     private String setVCardFromPhoneNumber(BluetoothMapbMessage message,
   3332             String phone, boolean incoming) {
   3333         String contactId = null, contactName = null;
   3334         String[] phoneNumbers = new String[1];
   3335         //Handle possible exception for empty phone address
   3336         if (TextUtils.isEmpty(phone)) {
   3337             return contactName;
   3338         }
   3339         //
   3340         // Use only actual phone number, because the MCE cannot know which
   3341         // number the message is from.
   3342         //
   3343         phoneNumbers[0] = phone;
   3344         String[] emailAddresses = null;
   3345         Cursor p;
   3346 
   3347         Uri uri = Uri
   3348                 .withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
   3349                 Uri.encode(phone));
   3350 
   3351         String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
   3352         String selection = Contacts.IN_VISIBLE_GROUP + "=1";
   3353         String orderBy = Contacts._ID + " ASC";
   3354 
   3355         // Get the contact _ID and name
   3356         p = mResolver.query(uri, projection, selection, null, orderBy);
   3357         try {
   3358             if (p != null && p.moveToFirst()) {
   3359                 contactId = p.getString(p.getColumnIndex(Contacts._ID));
   3360                 contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
   3361             }
   3362         } finally {
   3363             close(p);
   3364         }
   3365         // Bail out if we are unable to find a contact, based on the phone number
   3366         if (contactId != null) {
   3367             Cursor q = null;
   3368             // Fetch the contact e-mail addresses
   3369             try {
   3370                 q = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
   3371                         ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
   3372                         new String[]{contactId},
   3373                         null);
   3374                 if (q != null && q.moveToFirst()) {
   3375                     int i = 0;
   3376                     emailAddresses = new String[q.getCount()];
   3377                     do {
   3378                         String emailAddress = q.getString(q.getColumnIndex(
   3379                                 ContactsContract.CommonDataKinds.Email.ADDRESS));
   3380                         emailAddresses[i++] = emailAddress;
   3381                     } while (q != null && q.moveToNext());
   3382                 }
   3383             } finally {
   3384                 close(q);
   3385             }
   3386         }
   3387 
   3388         if (incoming == true) {
   3389             if(V) Log.d(TAG, "Adding originator for phone:" + phone);
   3390             // Use version 3.0 as we only have a formatted name
   3391             message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses,null,null);
   3392         } else {
   3393             if(V) Log.d(TAG, "Adding recipient for phone:" + phone);
   3394             // Use version 3.0 as we only have a formatted name
   3395             message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses,null,null);
   3396         }
   3397         return contactName;
   3398     }
   3399 
   3400     public static final int MAP_MESSAGE_CHARSET_NATIVE = 0;
   3401     public static final int MAP_MESSAGE_CHARSET_UTF8 = 1;
   3402 
   3403     public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException{
   3404         int type, threadId;
   3405         long time = -1;
   3406         String msgBody;
   3407         BluetoothMapbMessageSms message = new BluetoothMapbMessageSms();
   3408         TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
   3409 
   3410         Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null);
   3411         if (c == null || !c.moveToFirst()) {
   3412             throw new IllegalArgumentException("SMS handle not found");
   3413         }
   3414 
   3415         try{
   3416             if(c != null && c.moveToFirst())
   3417             {
   3418                 if(V) Log.v(TAG,"c.count: " + c.getCount());
   3419 
   3420                 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
   3421                     message.setType(TYPE.SMS_GSM);
   3422                 } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
   3423                     message.setType(TYPE.SMS_CDMA);
   3424                 }
   3425                 message.setVersionString(mMessageVersion);
   3426                 String read = c.getString(c.getColumnIndex(Sms.READ));
   3427                 if (read.equalsIgnoreCase("1"))
   3428                     message.setStatus(true);
   3429                 else
   3430                     message.setStatus(false);
   3431 
   3432                 type = c.getInt(c.getColumnIndex(Sms.TYPE));
   3433                 threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
   3434                 message.setFolder(getFolderName(type, threadId));
   3435 
   3436                 msgBody = c.getString(c.getColumnIndex(Sms.BODY));
   3437 
   3438                 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
   3439                 if ((phone == null) && type == Sms.MESSAGE_TYPE_DRAFT) {
   3440                     //Fetch address for Drafts folder from "canonical_address" table
   3441                     phone  = getCanonicalAddressSms(mResolver, threadId);
   3442                 }
   3443                 time = c.getLong(c.getColumnIndex(Sms.DATE));
   3444                 if(type == 1) // Inbox message needs to set the vCard as originator
   3445                     setVCardFromPhoneNumber(message, phone, true);
   3446                 else          // Other messages sets the vCard as the recipient
   3447                     setVCardFromPhoneNumber(message, phone, false);
   3448 
   3449                 if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
   3450                     if(type == 1) //Inbox
   3451                         message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody,
   3452                                     phone, time));
   3453                     else
   3454                         message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
   3455                 } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
   3456                     message.setSmsBody(msgBody);
   3457                 }
   3458                 return message.encode();
   3459             }
   3460         } finally {
   3461             if (c != null) c.close();
   3462         }
   3463 
   3464         return message.encode();
   3465     }
   3466 
   3467     private void extractMmsAddresses(long id, BluetoothMapbMessageMime message) {
   3468         final String[] projection = null;
   3469         String selection = new String(Mms.Addr.MSG_ID + "=" + id);
   3470         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
   3471         Uri uriAddress = Uri.parse(uriStr);
   3472         String contactName = null;
   3473 
   3474         Cursor c = mResolver.query( uriAddress, projection, selection, null, null);
   3475         try {
   3476             if (c.moveToFirst()) {
   3477                 do {
   3478                     String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
   3479                     if(address.equals(INSERT_ADDRES_TOKEN))
   3480                         continue;
   3481                     Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
   3482                     switch(type) {
   3483                     case MMS_FROM:
   3484                         contactName = setVCardFromPhoneNumber(message, address, true);
   3485                         message.addFrom(contactName, address);
   3486                         break;
   3487                     case MMS_TO:
   3488                         contactName = setVCardFromPhoneNumber(message, address, false);
   3489                         message.addTo(contactName, address);
   3490                         break;
   3491                     case MMS_CC:
   3492                         contactName = setVCardFromPhoneNumber(message, address, false);
   3493                         message.addCc(contactName, address);
   3494                         break;
   3495                     case MMS_BCC:
   3496                         contactName = setVCardFromPhoneNumber(message, address, false);
   3497                         message.addBcc(contactName, address);
   3498                         break;
   3499                     default:
   3500                         break;
   3501                     }
   3502                 } while(c.moveToNext());
   3503             }
   3504         } finally {
   3505             if (c != null) c.close();
   3506         }
   3507     }
   3508 
   3509 
   3510     /**
   3511      * Read out a mime data part and return the data in a byte array.
   3512      * @param contentPartUri TODO
   3513      * @param partid the content provider id of the Mime Part.
   3514      * @return
   3515      */
   3516     private byte[] readRawDataPart(Uri contentPartUri, long partid) {
   3517         String uriStr = new String(contentPartUri+"/"+ partid);
   3518         Uri uriAddress = Uri.parse(uriStr);
   3519         InputStream is = null;
   3520         ByteArrayOutputStream os = new ByteArrayOutputStream();
   3521         int bufferSize = 8192;
   3522         byte[] buffer = new byte[bufferSize];
   3523         byte[] retVal = null;
   3524 
   3525         try {
   3526             is = mResolver.openInputStream(uriAddress);
   3527             int len = 0;
   3528             while ((len = is.read(buffer)) != -1) {
   3529               os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
   3530             }
   3531             retVal = os.toByteArray();
   3532         } catch (IOException e) {
   3533             // do nothing for now
   3534             Log.w(TAG,"Error reading part data",e);
   3535         } finally {
   3536             close(os);
   3537             close(is);
   3538         }
   3539         return retVal;
   3540     }
   3541 
   3542     /**
   3543      * Read out the mms parts and update the bMessage object provided i {@linkplain message}
   3544      * @param id the content provider ID of the message
   3545      * @param message the bMessage object to add the information to
   3546      */
   3547     private void extractMmsParts(long id, BluetoothMapbMessageMime message)
   3548     {
   3549         /* Handling of filtering out non-text parts for exclude
   3550          * attachments is handled within the bMessage object. */
   3551         final String[] projection = null;
   3552         String selection = new String(Mms.Part.MSG_ID + "=" + id);
   3553         String uriStr = new String(Mms.CONTENT_URI + "/"+ id + "/part");
   3554         Uri uriAddress = Uri.parse(uriStr);
   3555         BluetoothMapbMessageMime.MimePart part;
   3556         Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
   3557         try {
   3558             if (c.moveToFirst()) {
   3559                 do {
   3560                     Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
   3561                     String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE));
   3562                     String name = c.getString(c.getColumnIndex(Mms.Part.NAME));
   3563                     String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET));
   3564                     String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME));
   3565                     String text = c.getString(c.getColumnIndex(Mms.Part.TEXT));
   3566                     Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA));
   3567                     String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID));
   3568                     String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION));
   3569                     String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION));
   3570 
   3571                     if(V) Log.d(TAG, "     _id : " + partId +
   3572                             "\n     ct : " + contentType +
   3573                             "\n     partname : " + name +
   3574                             "\n     charset : " + charset +
   3575                             "\n     filename : " + filename +
   3576                             "\n     text : " + text +
   3577                             "\n     fd : " + fd +
   3578                             "\n     cid : " + cid +
   3579                             "\n     cl : " + cl +
   3580                             "\n     cdisp : " + cdisp);
   3581 
   3582                     part = message.addMimePart();
   3583                     part.mContentType = contentType;
   3584                     part.mPartName = name;
   3585                     part.mContentId = cid;
   3586                     part.mContentLocation = cl;
   3587                     part.mContentDisposition = cdisp;
   3588 
   3589                     try {
   3590                         if(text != null) {
   3591                             part.mData = text.getBytes("UTF-8");
   3592                             part.mCharsetName = "utf-8";
   3593                         } else {
   3594                             part.mData =
   3595                                     readRawDataPart(Uri.parse(Mms.CONTENT_URI+"/part"), partId);
   3596                             if(charset != null) {
   3597                                 part.mCharsetName =
   3598                                         CharacterSets.getMimeName(Integer.parseInt(charset));
   3599                             }
   3600                         }
   3601                     } catch (NumberFormatException e) {
   3602                         Log.d(TAG,"extractMmsParts",e);
   3603                         part.mData = null;
   3604                         part.mCharsetName = null;
   3605                     } catch (UnsupportedEncodingException e) {
   3606                         Log.d(TAG,"extractMmsParts",e);
   3607                         part.mData = null;
   3608                         part.mCharsetName = null;
   3609                     } finally {
   3610                     }
   3611                     part.mFileName = filename;
   3612                 } while(c.moveToNext());
   3613                 message.updateCharset();
   3614             }
   3615 
   3616         } finally {
   3617             if(c != null) c.close();
   3618         }
   3619     }
   3620     /**
   3621      * Read out the mms parts and update the bMessage object provided i {@linkplain message}
   3622      * @param id the content provider ID of the message
   3623      * @param message the bMessage object to add the information to
   3624      */
   3625     private void extractIMParts(long id, BluetoothMapbMessageMime message)
   3626     {
   3627         /* Handling of filtering out non-text parts for exclude
   3628          * attachments is handled within the bMessage object. */
   3629         final String[] projection = null;
   3630         String selection = new String(BluetoothMapContract.MessageColumns._ID + "=" + id);
   3631         String uriStr = new String(mBaseUri
   3632                                          + BluetoothMapContract.TABLE_MESSAGE + "/"+ id + "/part");
   3633         Uri uriAddress = Uri.parse(uriStr);
   3634         BluetoothMapbMessageMime.MimePart part;
   3635         Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
   3636         try{
   3637             if (c.moveToFirst()) {
   3638                 do {
   3639                     Long partId = c.getLong(
   3640                                   c.getColumnIndex(BluetoothMapContract.MessagePartColumns._ID));
   3641                     String charset = c.getString(
   3642                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CHARSET));
   3643                     String filename = c.getString(
   3644                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.FILENAME));
   3645                     String text = c.getString(
   3646                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.TEXT));
   3647                     String body = c.getString(
   3648                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.RAW_DATA));
   3649                     String cid = c.getString(
   3650                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CONTENT_ID));
   3651 
   3652                     if(V) Log.d(TAG, "     _id : " + partId +
   3653                             "\n     charset : " + charset +
   3654                             "\n     filename : " + filename +
   3655                             "\n     text : " + text +
   3656                             "\n     cid : " + cid);
   3657 
   3658                     part = message.addMimePart();
   3659                     part.mContentId = cid;
   3660                     try {
   3661                         if(text.equalsIgnoreCase("yes")) {
   3662                             part.mData = body.getBytes("UTF-8");
   3663                             part.mCharsetName = "utf-8";
   3664                         } else {
   3665                             part.mData = readRawDataPart(Uri.parse(mBaseUri
   3666                                              + BluetoothMapContract.TABLE_MESSAGE_PART) , partId);
   3667                             if(charset != null)
   3668                                 part.mCharsetName = CharacterSets.getMimeName(
   3669                                                                         Integer.parseInt(charset));
   3670                         }
   3671                     } catch (NumberFormatException e) {
   3672                         Log.d(TAG,"extractIMParts",e);
   3673                         part.mData = null;
   3674                         part.mCharsetName = null;
   3675                     } catch (UnsupportedEncodingException e) {
   3676                         Log.d(TAG,"extractIMParts",e);
   3677                         part.mData = null;
   3678                         part.mCharsetName = null;
   3679                     } finally {
   3680                     }
   3681                     part.mFileName = filename;
   3682                 } while(c.moveToNext());
   3683             }
   3684         } finally {
   3685             if(c != null) c.close();
   3686         }
   3687 
   3688         message.updateCharset();
   3689     }
   3690 
   3691     /**
   3692      *
   3693      * @param id the content provider id for the message to fetch.
   3694      * @param appParams The application parameter object received from the client.
   3695      * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
   3696      * @throws UnsupportedEncodingException if UTF-8 is not supported,
   3697      * which is guaranteed to be supported on an android device
   3698      */
   3699     public byte[] getMmsMessage(long id,BluetoothMapAppParams appParams)
   3700                                                         throws UnsupportedEncodingException {
   3701         int msgBox, threadId;
   3702         if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
   3703             throw new IllegalArgumentException("MMS charset native not allowed for MMS"
   3704                                                                             +" - must be utf-8");
   3705 
   3706         BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
   3707         Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null);
   3708         try {
   3709             if(c != null && c.moveToFirst())
   3710             {
   3711                 message.setType(TYPE.MMS);
   3712                 message.setVersionString(mMessageVersion);
   3713 
   3714                 // The MMS info:
   3715                 String read = c.getString(c.getColumnIndex(Mms.READ));
   3716                 if (read.equalsIgnoreCase("1"))
   3717                     message.setStatus(true);
   3718                 else
   3719                     message.setStatus(false);
   3720 
   3721                 msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
   3722                 threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
   3723                 message.setFolder(getFolderName(msgBox, threadId));
   3724                 message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT)));
   3725                 message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
   3726                 message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
   3727                 message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
   3728                 message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true);
   3729                 message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
   3730                 // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
   3731                 // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
   3732 
   3733                 // The parts
   3734                 extractMmsParts(id, message);
   3735 
   3736                 // The addresses
   3737                 extractMmsAddresses(id, message);
   3738 
   3739 
   3740                 return message.encode();
   3741             }
   3742         } finally {
   3743             if (c != null) c.close();
   3744         }
   3745 
   3746         return message.encode();
   3747     }
   3748 
   3749     /**
   3750     *
   3751     * @param id the content provider id for the message to fetch.
   3752     * @param appParams The application parameter object received from the client.
   3753     * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
   3754     * @throws UnsupportedEncodingException if UTF-8 is not supported,
   3755     * which is guaranteed to be supported on an android device
   3756     */
   3757    public byte[] getEmailMessage(long id, BluetoothMapAppParams appParams,
   3758            BluetoothMapFolderElement currentFolder) throws UnsupportedEncodingException {
   3759        // Log print out of application parameters set
   3760        if(D && appParams != null) {
   3761            Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() +
   3762                    ", Charset = " + appParams.getCharset() +
   3763                    ", FractionRequest = " + appParams.getFractionRequest());
   3764        }
   3765 
   3766        // Throw exception if requester NATIVE charset for Email
   3767        // Exception is caught by MapObexServer sendGetMessageResp
   3768        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
   3769            throw new IllegalArgumentException("EMAIL charset not UTF-8");
   3770 
   3771        BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail();
   3772        Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
   3773        Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = "
   3774                + id, null, null);
   3775        try {
   3776            if(c != null && c.moveToFirst())
   3777            {
   3778                BluetoothMapFolderElement folderElement;
   3779                FileInputStream is = null;
   3780                ParcelFileDescriptor fd = null;
   3781                try {
   3782                    // Handle fraction requests
   3783                    int fractionRequest = appParams.getFractionRequest();
   3784                    if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
   3785                        // Fraction requested
   3786                        if(V) {
   3787                            String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
   3788                            Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
   3789                                    +  " - send compete message" );
   3790                        }
   3791                        // Check if message is complete and if not - request message from server
   3792                        if (c.getString(c.getColumnIndex(
   3793                                BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase(
   3794                                        BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false)  {
   3795                            // TODO: request message from server
   3796                            Log.w(TAG, "getEmailMessage - receptionState not COMPLETE -  Not Implemented!" );
   3797                        }
   3798                    }
   3799                    // Set read status:
   3800                    String read = c.getString(
   3801                                         c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
   3802                    if (read != null && read.equalsIgnoreCase("1"))
   3803                        message.setStatus(true);
   3804                    else
   3805                        message.setStatus(false);
   3806 
   3807                    // Set message type:
   3808                    message.setType(TYPE.EMAIL);
   3809                    message.setVersionString(mMessageVersion);
   3810                    // Set folder:
   3811                    long folderId = c.getLong(
   3812                                        c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
   3813                    folderElement = currentFolder.getFolderById(folderId);
   3814                    message.setCompleteFolder(folderElement.getFullPath());
   3815 
   3816                    // Set recipient:
   3817                    String nameEmail = c.getString(
   3818                                        c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
   3819                    Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
   3820                    if (tokens.length != 0) {
   3821                        if(D) Log.d(TAG, "Recipient count= " + tokens.length);
   3822                        int i = 0;
   3823                        while (i < tokens.length) {
   3824                            if(V) Log.d(TAG, "Recipient = " + tokens[i].toString());
   3825                            String[] emails = new String[1];
   3826                            emails[0] = tokens[i].getAddress();
   3827                            String name = tokens[i].getName();
   3828                            message.addRecipient(name, name, null, emails, null, null);
   3829                            i++;
   3830                        }
   3831                    }
   3832 
   3833                    // Set originator:
   3834                    nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
   3835                    tokens = Rfc822Tokenizer.tokenize(nameEmail);
   3836                    if (tokens.length != 0) {
   3837                        if(D) Log.d(TAG, "Originator count= " + tokens.length);
   3838                        int i = 0;
   3839                        while (i < tokens.length) {
   3840                            if(V) Log.d(TAG, "Originator = " + tokens[i].toString());
   3841                            String[] emails = new String[1];
   3842                            emails[0] = tokens[i].getAddress();
   3843                            String name = tokens[i].getName();
   3844                            message.addOriginator(name, name, null, emails, null, null);
   3845                            i++;
   3846                        }
   3847                    }
   3848                } finally {
   3849                    if(c != null) c.close();
   3850                }
   3851                // Find out if we get attachments
   3852                String attStr = (appParams.getAttachment() == 0) ?
   3853                                            "/" +  BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
   3854                Uri uri = Uri.parse(contentUri + "/" + id + attStr);
   3855 
   3856                // Get email message body content
   3857                int count = 0;
   3858                try {
   3859                    fd = mResolver.openFileDescriptor(uri, "r");
   3860                    is = new FileInputStream(fd.getFileDescriptor());
   3861                    StringBuilder email = new StringBuilder("");
   3862                    byte[] buffer = new byte[1024];
   3863                    while((count = is.read(buffer)) != -1) {
   3864                        // TODO: Handle breaks within a UTF8 character
   3865                        email.append(new String(buffer,0,count));
   3866                        if(V) Log.d(TAG, "Email part = "
   3867                                          + new String(buffer,0,count)
   3868                                          + " count=" + count);
   3869                    }
   3870                    // Set email message body:
   3871                    message.setEmailBody(email.toString());
   3872                } catch (FileNotFoundException e) {
   3873                    Log.w(TAG, e);
   3874                } catch (NullPointerException e) {
   3875                    Log.w(TAG, e);
   3876                } catch (IOException e) {
   3877                    Log.w(TAG, e);
   3878                } finally {
   3879                    try {
   3880                        if(is != null) is.close();
   3881                    } catch (IOException e) {}
   3882                    try {
   3883                        if(fd != null) fd.close();
   3884                    } catch (IOException e) {}
   3885                }
   3886                return message.encode();
   3887            }
   3888        } finally {
   3889            if (c != null) c.close();
   3890        }
   3891        throw new IllegalArgumentException("EMAIL handle not found");
   3892    }
   3893    /**
   3894    *
   3895    * @param id the content provider id for the message to fetch.
   3896    * @param appParams The application parameter object received from the client.
   3897    * @return a byte[] containing the UTF-8 encoded bMessage to send to the client.
   3898    * @throws UnsupportedEncodingException if UTF-8 is not supported,
   3899    * which is guaranteed to be supported on an android device
   3900    */
   3901 
   3902    /**
   3903    *
   3904    * @param id the content provider id for the message to fetch.
   3905    * @param appParams The application parameter object received from the client.
   3906    * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
   3907    * @throws UnsupportedEncodingException if UTF-8 is not supported,
   3908    * which is guaranteed to be supported on an android device
   3909    */
   3910    public byte[] getIMMessage(long id,
   3911            BluetoothMapAppParams appParams,
   3912            BluetoothMapFolderElement folderElement)
   3913                    throws UnsupportedEncodingException {
   3914        long threadId, folderId;
   3915 
   3916        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
   3917            throw new IllegalArgumentException(
   3918                    "IM charset native not allowed for IM - must be utf-8");
   3919 
   3920        BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
   3921        Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
   3922        Cursor c = mResolver.query(contentUri,
   3923                BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
   3924        Cursor contacts = null;
   3925        try {
   3926            if(c != null && c.moveToFirst()) {
   3927                message.setType(TYPE.IM);
   3928                message.setVersionString(mMessageVersion);
   3929 
   3930                // The IM message info:
   3931                int read =
   3932                        c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
   3933                if (read == 1)
   3934                    message.setStatus(true);
   3935                else
   3936                    message.setStatus(false);
   3937 
   3938                threadId =
   3939                        c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID));
   3940                folderId =
   3941                        c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
   3942                folderElement = folderElement.getFolderById(folderId);
   3943                message.setCompleteFolder(folderElement.getFullPath());
   3944                message.setSubject(c.getString(
   3945                        c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT)));
   3946                message.setMessageId(c.getString(
   3947                        c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)));
   3948                message.setDate(c.getLong(
   3949                        c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE)));
   3950                message.setTextOnly(c.getInt(c.getColumnIndex(
   3951                        BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE)) != 0 ? false : true);
   3952 
   3953                message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
   3954 
   3955                // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
   3956                // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
   3957 
   3958                // The parts
   3959 
   3960                //FIXME use the parts when ready - until then use the body column for text-only
   3961                //  extractIMParts(id, message);
   3962                //FIXME next few lines are temporary code
   3963                MimePart part = message.addMimePart();
   3964                part.mData = c.getString((c.getColumnIndex(
   3965                        BluetoothMapContract.MessageColumns.BODY))).getBytes("UTF-8");
   3966                part.mCharsetName = "utf-8";
   3967                part.mContentId = "0";
   3968                part.mContentType = "text/plain";
   3969                message.updateCharset();
   3970                // FIXME end temp code
   3971 
   3972                Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
   3973                contacts = mResolver.query(contactsUri,
   3974                        BluetoothMapContract.BT_CONTACT_PROJECTION,
   3975                        BluetoothMapContract.ConvoContactColumns.CONVO_ID
   3976                        + " = " + threadId, null, null);
   3977                // TODO this will not work for group-chats
   3978                if(contacts != null && contacts.moveToFirst()){
   3979                    String name = contacts.getString(contacts.getColumnIndex(
   3980                            BluetoothMapContract.ConvoContactColumns.NAME));
   3981                    String btUid[] = new String[1];
   3982                    btUid[0]= contacts.getString(contacts.getColumnIndex(
   3983                            BluetoothMapContract.ConvoContactColumns.X_BT_UID));
   3984                    String nickname = contacts.getString(contacts.getColumnIndex(
   3985                            BluetoothMapContract.ConvoContactColumns.NICKNAME));
   3986                    String btUci[] = new String[1];
   3987                    String btOwnUci[] = new String[1];
   3988                    btOwnUci[0] = mAccount.getUciFull();
   3989                    btUci[0] = contacts.getString(contacts.getColumnIndex(
   3990                            BluetoothMapContract.ConvoContactColumns.UCI));
   3991                    if(folderId == BluetoothMapContract.FOLDER_ID_SENT
   3992                            || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) {
   3993                        message.addRecipient(nickname,name,null, null, btUid, btUci);
   3994                        message.addOriginator(null, btOwnUci);
   3995 
   3996                    }else {
   3997                        message.addOriginator(nickname,name,null, null, btUid, btUci);
   3998                        message.addRecipient(null, btOwnUci);
   3999 
   4000                    }
   4001                }
   4002                return message.encode();
   4003            }
   4004        } finally {
   4005            if(c != null) c.close();
   4006            if(contacts != null) contacts.close();
   4007        }
   4008 
   4009        throw new IllegalArgumentException("IM handle not found");
   4010    }
   4011 
   4012    public void setRemoteFeatureMask(int featureMask){
   4013        this.mRemoteFeatureMask = featureMask;
   4014        if(V) Log.d(TAG, "setRemoteFeatureMask");
   4015        if((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
   4016                == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
   4017            if(V) Log.d(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11");
   4018            this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
   4019        }
   4020    }
   4021 
   4022    public int getRemoteFeatureMask(){
   4023        return this.mRemoteFeatureMask;
   4024    }
   4025 
   4026     HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
   4027         return mMasInstance.getSmsMmsConvoList();
   4028     }
   4029 
   4030     void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
   4031         mMasInstance.setSmsMmsConvoList(smsMmsConvoList);
   4032     }
   4033 
   4034     HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
   4035         return mMasInstance.getImEmailConvoList();
   4036     }
   4037 
   4038     void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
   4039         mMasInstance.setImEmailConvoList(imEmailConvoList);
   4040     }
   4041 }
   4042