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