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