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