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