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