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