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