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