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