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