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