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