• 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 = "1=1";
1660         if (!folderElement.shouldIgnore()) {
1661             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1662                 where = setWhereFilterFolderTypeSms(folderElement.getName());
1663             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1664                 where = setWhereFilterFolderTypeMms(folderElement.getName());
1665             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
1666                 where = setWhereFilterFolderTypeEmail(folderElement.getFolderId());
1667             } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
1668                 where = setWhereFilterFolderTypeIm(folderElement.getFolderId());
1669             }
1670         }
1671 
1672         return where;
1673     }
1674 
setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi)1675     private String setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi) {
1676         String where = "";
1677         if (ap.getFilterReadStatus() != -1) {
1678             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1679                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
1680                     where = " AND " + Sms.READ + "= 0";
1681                 }
1682 
1683                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
1684                     where = " AND " + Sms.READ + "= 1";
1685                 }
1686             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1687                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
1688                     where = " AND " + Mms.READ + "= 0";
1689                 }
1690 
1691                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
1692                     where = " AND " + Mms.READ + "= 1";
1693                 }
1694             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
1695                        fi.mMsgType == FilterInfo.TYPE_IM) {
1696                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
1697                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0";
1698                 }
1699                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
1700                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 1";
1701                 }
1702             }
1703         }
1704         return where;
1705     }
1706 
setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi)1707     private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) {
1708         String where = "";
1709 
1710         if ((ap.getFilterPeriodBegin() != -1)) {
1711             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1712                 where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin();
1713             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1714                 where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L);
1715             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
1716                        fi.mMsgType == FilterInfo.TYPE_IM) {
1717                 where = " AND " + BluetoothMapContract.MessageColumns.DATE +
1718                         " >= " + (ap.getFilterPeriodBegin());
1719             }
1720         }
1721 
1722         if ((ap.getFilterPeriodEnd() != -1)) {
1723             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1724                 where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd();
1725             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1726                 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
1727             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
1728                        fi.mMsgType == FilterInfo.TYPE_IM) {
1729                 where += " AND " + BluetoothMapContract.MessageColumns.DATE +
1730                         " < " + (ap.getFilterPeriodEnd());
1731             }
1732         }
1733         return where;
1734     }
setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi)1735     private String setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi) {
1736             String where = "";
1737         if ((ap.getFilterLastActivityBegin() != -1)) {
1738             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1739                 where = " AND " + Sms.DATE + " >= " + ap.getFilterLastActivityBegin();
1740             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1741                 where = " AND " + Mms.DATE + " >= " + (ap.getFilterLastActivityBegin() / 1000L);
1742             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
1743                       fi.mMsgType == FilterInfo.TYPE_IM ) {
1744                 where = " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY +
1745                         " >= " + (ap.getFilterPeriodBegin());
1746             }
1747         }
1748         if ((ap.getFilterLastActivityEnd() != -1)) {
1749             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1750                 where += " AND " + Sms.DATE + " < " + ap.getFilterLastActivityEnd();
1751             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1752                 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
1753             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||fi.mMsgType == FilterInfo.TYPE_IM) {
1754                 where += " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
1755                       + " < " + (ap.getFilterLastActivityEnd());
1756             }
1757         }
1758         return where;
1759     }
1760 
1761 
setWhereFilterOriginatorEmail(BluetoothMapAppParams ap)1762     private String setWhereFilterOriginatorEmail(BluetoothMapAppParams ap) {
1763         String where = "";
1764         String orig = ap.getFilterOriginator();
1765 
1766         /* Be aware of wild cards in the beginning of string, may not be valid? */
1767         if (orig != null && orig.length() > 0) {
1768             orig = orig.replace("*", "%");
1769             where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
1770                     + " LIKE '%" +  orig + "%'";
1771         }
1772         return where;
1773     }
1774 
setWhereFilterOriginatorIM(BluetoothMapAppParams ap)1775     private String setWhereFilterOriginatorIM(BluetoothMapAppParams ap) {
1776         String where = "";
1777         String orig = ap.getFilterOriginator();
1778 
1779         /* Be aware of wild cards in the beginning of string, may not be valid? */
1780         if (orig != null && orig.length() > 0) {
1781             orig = orig.replace("*", "%");
1782             where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
1783                     + " LIKE '%" +  orig + "%'";
1784         }
1785         return where;
1786     }
1787 
setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi)1788     private String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) {
1789         String where = "";
1790         int pri = ap.getFilterPriority();
1791         /*only MMS have priority info */
1792         if(fi.mMsgType == FilterInfo.TYPE_MMS)
1793         {
1794             if(pri == 0x0002)
1795             {
1796                 where += " AND " + Mms.PRIORITY + "<=" +
1797                     Integer.toString(PduHeaders.PRIORITY_NORMAL);
1798             }else if(pri == 0x0001) {
1799                 where += " AND " + Mms.PRIORITY + "=" +
1800                     Integer.toString(PduHeaders.PRIORITY_HIGH);
1801             }
1802         }
1803         if(fi.mMsgType == FilterInfo.TYPE_EMAIL ||
1804            fi.mMsgType == FilterInfo.TYPE_IM)
1805         {
1806             if(pri == 0x0002)
1807             {
1808                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "!=1";
1809             }else if(pri == 0x0001) {
1810                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "=1";
1811             }
1812         }
1813         // TODO: no priority filtering in IM
1814         return where;
1815     }
1816 
setWhereFilterRecipientEmail(BluetoothMapAppParams ap)1817     private String setWhereFilterRecipientEmail(BluetoothMapAppParams ap) {
1818         String where = "";
1819         String recip = ap.getFilterRecipient();
1820 
1821         /* Be aware of wild cards in the beginning of string, may not be valid? */
1822         if (recip != null && recip.length() > 0) {
1823             recip = recip.replace("*", "%");
1824             where = " AND ("
1825             + BluetoothMapContract.MessageColumns.TO_LIST  + " LIKE '%" + recip + "%' OR "
1826             + BluetoothMapContract.MessageColumns.CC_LIST  + " LIKE '%" + recip + "%' OR "
1827             + BluetoothMapContract.MessageColumns.BCC_LIST + " LIKE '%" + recip + "%' )";
1828         }
1829         return where;
1830     }
1831 
setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi)1832     private String setWhereFilterMessageHandle(BluetoothMapAppParams ap, FilterInfo fi) {
1833         String where = "";
1834         long id = -1;
1835         String msgHandle = ap.getFilterMsgHandleString();
1836         if(msgHandle != null) {
1837             id = BluetoothMapUtils.getCpHandle(msgHandle);
1838             if(D)Log.d(TAG,"id: " + id);
1839         }
1840         if(id != -1) {
1841             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1842                where = " AND " + Sms._ID + " = " + id;
1843             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1844                 where = " AND " + Mms._ID + " = " + id;
1845             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
1846                        fi.mMsgType == FilterInfo.TYPE_IM) {
1847                 where = " AND " + BluetoothMapContract.MessageColumns._ID + " = " + id;
1848             }
1849         }
1850         return where;
1851     }
1852 
setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi)1853     private String setWhereFilterThreadId(BluetoothMapAppParams ap, FilterInfo fi) {
1854         String where = "";
1855         long id = -1;
1856         String msgHandle = ap.getFilterConvoIdString();
1857         if(msgHandle != null) {
1858             id = BluetoothMapUtils.getMsgHandleAsLong(msgHandle);
1859             if(D)Log.d(TAG,"id: " + id);
1860         }
1861         if(id > 0) {
1862             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1863                where = " AND " + Sms.THREAD_ID + " = " + id;
1864             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1865                 where = " AND " + Mms.THREAD_ID + " = " + id;
1866             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
1867                        fi.mMsgType == FilterInfo.TYPE_IM) {
1868                 where = " AND " + BluetoothMapContract.MessageColumns.THREAD_ID + " = " + id;
1869             }
1870         }
1871 
1872         return where;
1873     }
1874 
setWhereFilter(BluetoothMapFolderElement folderElement, FilterInfo fi, BluetoothMapAppParams ap)1875     private String setWhereFilter(BluetoothMapFolderElement folderElement,
1876             FilterInfo fi, BluetoothMapAppParams ap) {
1877         String where = "";
1878         where += setWhereFilterFolderType(folderElement, fi);
1879 
1880         String msgHandleWhere = setWhereFilterMessageHandle(ap, fi);
1881         /* if message handle filter is available, the other filters should be ignored */
1882         if(msgHandleWhere.isEmpty()) {
1883             where += setWhereFilterReadStatus(ap, fi);
1884             where += setWhereFilterPriority(ap,fi);
1885             where += setWhereFilterPeriod(ap, fi);
1886             if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
1887                 where += setWhereFilterOriginatorEmail(ap);
1888                 where += setWhereFilterRecipientEmail(ap);
1889             }
1890             if (fi.mMsgType == FilterInfo.TYPE_IM) {
1891                 where += setWhereFilterOriginatorIM(ap);
1892                 // TODO: set 'where' filer recipient?
1893             }
1894             where += setWhereFilterThreadId(ap, fi);
1895         } else {
1896             where += msgHandleWhere;
1897         }
1898 
1899         return where;
1900     }
1901 
1902 
1903     /* Used only for SMS/MMS */
setConvoWhereFilterSmsMms(StringBuilder selection, ArrayList<String> selectionArgs, FilterInfo fi, BluetoothMapAppParams ap)1904     private void setConvoWhereFilterSmsMms(StringBuilder selection, ArrayList<String> selectionArgs,
1905             FilterInfo fi, BluetoothMapAppParams ap) {
1906 
1907         if (smsSelected(fi, ap) || mmsSelected(ap)) {
1908 
1909             // Filter Read Status
1910             if(ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
1911                 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_UNREAD_ONLY) != 0) {
1912                     selection.append(" AND ").append(Threads.READ).append(" = 0");
1913                 }
1914                 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_READ_ONLY) != 0) {
1915                     selection.append(" AND ").append(Threads.READ).append(" = 1");
1916                 }
1917             }
1918 
1919             // Filter time
1920             if ((ap.getFilterLastActivityBegin() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)){
1921                 selection.append(" AND ").append(Threads.DATE).append(" >= ")
1922                 .append(ap.getFilterLastActivityBegin());
1923             }
1924             if ((ap.getFilterLastActivityEnd() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) {
1925                 selection.append(" AND ").append(Threads.DATE).append(" <= ")
1926                 .append(ap.getFilterLastActivityEnd());
1927             }
1928 
1929             // Filter ConvoId
1930             long convoId = -1;
1931             if(ap.getFilterConvoId() != null) {
1932                 convoId = ap.getFilterConvoId().getLeastSignificantBits();
1933             }
1934             if(convoId > 0) {
1935                 selection.append(" AND ").append(Threads._ID).append(" = ")
1936                 .append(Long.toString(convoId));
1937             }
1938         }
1939     }
1940 
1941 
1942 
1943     /**
1944      * Determine from application parameter if sms should be included.
1945      * The filter mask is set for message types not selected
1946      * @param fi
1947      * @param ap
1948      * @return boolean true if sms is selected, false if not
1949      */
smsSelected(FilterInfo fi, BluetoothMapAppParams ap)1950     private boolean smsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
1951         int msgType = ap.getFilterMessageType();
1952         int phoneType = fi.mPhoneType;
1953 
1954         if (D) Log.d(TAG, "smsSelected msgType: " + msgType);
1955 
1956         if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
1957             return true;
1958 
1959         if ((msgType & (BluetoothMapAppParams.FILTER_NO_SMS_CDMA
1960                 |BluetoothMapAppParams.FILTER_NO_SMS_GSM)) == 0)
1961             return true;
1962 
1963         if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0)
1964                 && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
1965             return true;
1966 
1967         if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0)
1968                 && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
1969             return true;
1970 
1971         return false;
1972     }
1973 
1974     /**
1975      * Determine from application parameter if mms should be included.
1976      * The filter mask is set for message types not selected
1977      * @param fi
1978      * @param ap
1979      * @return boolean true if mms is selected, false if not
1980      */
mmsSelected(BluetoothMapAppParams ap)1981     private boolean mmsSelected(BluetoothMapAppParams ap) {
1982         int msgType = ap.getFilterMessageType();
1983 
1984         if (D) Log.d(TAG, "mmsSelected msgType: " + msgType);
1985 
1986         if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
1987             return true;
1988 
1989         if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0)
1990             return true;
1991 
1992         return false;
1993     }
1994 
1995     /**
1996      * Determine from application parameter if email should be included.
1997      * The filter mask is set for message types not selected
1998      * @param fi
1999      * @param ap
2000      * @return boolean true if email is selected, false if not
2001      */
emailSelected(BluetoothMapAppParams ap)2002     private boolean emailSelected(BluetoothMapAppParams ap) {
2003         int msgType = ap.getFilterMessageType();
2004 
2005         if (D) Log.d(TAG, "emailSelected msgType: " + msgType);
2006 
2007         if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
2008             return true;
2009 
2010         if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0)
2011             return true;
2012 
2013         return false;
2014     }
2015 
2016     /**
2017      * Determine from application parameter if IM should be included.
2018      * The filter mask is set for message types not selected
2019      * @param fi
2020      * @param ap
2021      * @return boolean true if im is selected, false if not
2022      */
imSelected(BluetoothMapAppParams ap)2023     private boolean imSelected(BluetoothMapAppParams ap) {
2024         int msgType = ap.getFilterMessageType();
2025 
2026         if (D) Log.d(TAG, "imSelected msgType: " + msgType);
2027 
2028         if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
2029             return true;
2030 
2031         if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0)
2032             return true;
2033 
2034         return false;
2035     }
2036 
setFilterInfo(FilterInfo fi)2037     private void setFilterInfo(FilterInfo fi) {
2038         TelephonyManager tm =
2039             (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
2040         if (tm != null) {
2041             fi.mPhoneType = tm.getPhoneType();
2042             fi.mPhoneNum = tm.getLine1Number();
2043             fi.mPhoneAlphaTag = tm.getLine1AlphaTag();
2044             if (D) Log.d(TAG, "phone type = " + fi.mPhoneType +
2045                 " phone num = " + fi.mPhoneNum +
2046                 " phone alpha tag = " + fi.mPhoneAlphaTag);
2047         }
2048     }
2049 
2050     /**
2051      * Get a listing of message in folder after applying filter.
2052      * @param folder Must contain a valid folder string != null
2053      * @param ap Parameters specifying message content and filters
2054      * @return Listing object containing requested messages
2055      */
msgListing(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2056     public BluetoothMapMessageListing msgListing(BluetoothMapFolderElement folderElement,
2057             BluetoothMapAppParams ap) {
2058         if (D) Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType() );
2059 
2060         BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
2061 
2062         /* We overwrite the parameter mask here if it is 0 or not present, as this
2063          * should cause all parameters to be included in the message list. */
2064         if(ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
2065                 ap.getParameterMask() == 0) {
2066             ap.setParameterMask(PARAMETER_MASK_DEFAULT);
2067             if (V) Log.v(TAG, "msgListing(): appParameterMask is zero or not present, " +
2068                     "changing to default: " + ap.getParameterMask());
2069         }
2070         if (V) Log.v(TAG, "folderElement hasSmsMmsContent = " + folderElement.hasSmsMmsContent() +
2071                 " folderElement.hasEmailContent = " + folderElement.hasEmailContent() +
2072                 " folderElement.hasImContent = " + folderElement.hasImContent());
2073 
2074         /* Cache some info used throughout filtering */
2075         FilterInfo fi = new FilterInfo();
2076         setFilterInfo(fi);
2077         Cursor smsCursor = null;
2078         Cursor mmsCursor = null;
2079         Cursor emailCursor = null;
2080         Cursor imCursor = null;
2081         String limit = "";
2082         int countNum = ap.getMaxListCount();
2083         int offsetNum = ap.getStartOffset();
2084         if(ap.getMaxListCount()>0){
2085             limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset());
2086         }
2087         try{
2088             if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
2089                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
2090                                                  BluetoothMapAppParams.FILTER_NO_MMS|
2091                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM|
2092                                                  BluetoothMapAppParams.FILTER_NO_IM)||
2093                    ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
2094                                                  BluetoothMapAppParams.FILTER_NO_MMS|
2095                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
2096                                                  BluetoothMapAppParams.FILTER_NO_IM)){
2097                     //set real limit and offset if only this type is used
2098                     // (only if offset/limit is used)
2099                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
2100                     if(D) Log.d(TAG, "SMS Limit => "+limit);
2101                     offsetNum = 0;
2102                 }
2103                 fi.mMsgType = FilterInfo.TYPE_SMS;
2104                 if(ap.getFilterPriority() != 1){ /*SMS cannot have high priority*/
2105                     String where = setWhereFilter(folderElement, fi, ap);
2106                     if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
2107                     smsCursor = mResolver.query(Sms.CONTENT_URI,
2108                             SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit);
2109                     if (smsCursor != null) {
2110                         BluetoothMapMessageListingElement e = null;
2111                         // store column index so we dont have to look them up anymore (optimization)
2112                         if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
2113                         fi.setSmsColumns(smsCursor);
2114                         while (smsCursor.moveToNext()) {
2115                             if (matchAddresses(smsCursor, fi, ap)) {
2116                                 if(V) BluetoothMapUtils.printCursor(smsCursor);
2117                                 e = element(smsCursor, fi, ap);
2118                                 bmList.add(e);
2119                             }
2120                         }
2121                     }
2122                 }
2123             }
2124 
2125             if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
2126                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
2127                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
2128                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM|
2129                                                  BluetoothMapAppParams.FILTER_NO_IM)){
2130                     //set real limit and offset if only this type is used
2131                     //(only if offset/limit is used)
2132                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
2133                     if(D) Log.d(TAG, "MMS Limit => "+limit);
2134                     offsetNum = 0;
2135                 }
2136                 fi.mMsgType = FilterInfo.TYPE_MMS;
2137                 String where = setWhereFilter(folderElement, fi, ap);
2138                 where += " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE;
2139                 if(!where.isEmpty()) {
2140                     if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
2141                     mmsCursor = mResolver.query(Mms.CONTENT_URI,
2142                             MMS_PROJECTION, where, null, Mms.DATE + " DESC" + limit);
2143                     if (mmsCursor != null) {
2144                         BluetoothMapMessageListingElement e = null;
2145                         // store column index so we dont have to look them up anymore (optimization)
2146                         fi.setMmsColumns(mmsCursor);
2147                         if(D) Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages.");
2148                         while (mmsCursor.moveToNext()) {
2149                             if (matchAddresses(mmsCursor, fi, ap)) {
2150                                 if(V) BluetoothMapUtils.printCursor(mmsCursor);
2151                                 e = element(mmsCursor, fi, ap);
2152                                 bmList.add(e);
2153                             }
2154                         }
2155                     }
2156                 }
2157             }
2158 
2159             if (emailSelected(ap) && folderElement.hasEmailContent()) {
2160                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
2161                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
2162                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM|
2163                                                  BluetoothMapAppParams.FILTER_NO_IM)){
2164                     //set real limit and offset if only this type is used
2165                     //(only if offset/limit is used)
2166                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
2167                     if(D) Log.d(TAG, "Email Limit => "+limit);
2168                     offsetNum = 0;
2169                 }
2170                 fi.mMsgType = FilterInfo.TYPE_EMAIL;
2171                 String where = setWhereFilter(folderElement, fi, ap);
2172 
2173                 if(!where.isEmpty()) {
2174                     if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
2175                     Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
2176                     emailCursor = mResolver.query(contentUri,
2177                             BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
2178                             BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
2179                     if (emailCursor != null) {
2180                         BluetoothMapMessageListingElement e = null;
2181                         // store column index so we dont have to look them up anymore (optimization)
2182                         fi.setEmailMessageColumns(emailCursor);
2183                         int cnt = 0;
2184                         if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
2185                         while (emailCursor.moveToNext()) {
2186                             if(V) BluetoothMapUtils.printCursor(emailCursor);
2187                             e = element(emailCursor, fi, ap);
2188                             bmList.add(e);
2189                         }
2190                     //   emailCursor.close();
2191                     }
2192                 }
2193             }
2194 
2195             if (imSelected(ap) && folderElement.hasImContent()) {
2196                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
2197                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
2198                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM|
2199                                                  BluetoothMapAppParams.FILTER_NO_EMAIL)){
2200                     //set real limit and offset if only this type is used
2201                     //(only if offset/limit is used)
2202                     limit = " LIMIT " + ap.getMaxListCount() + " OFFSET "+ ap.getStartOffset();
2203                     if(D) Log.d(TAG, "IM Limit => "+limit);
2204                     offsetNum = 0;
2205                 }
2206                 fi.mMsgType = FilterInfo.TYPE_IM;
2207                 String where = setWhereFilter(folderElement, fi, ap);
2208                 if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
2209 
2210                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
2211                 imCursor = mResolver.query(contentUri,
2212                         BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
2213                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
2214                 if (imCursor != null) {
2215                     BluetoothMapMessageListingElement e = null;
2216                     // store column index so we dont have to look them up anymore (optimization)
2217                     fi.setImMessageColumns(imCursor);
2218                     if (D) Log.d(TAG, "Found " + imCursor.getCount() + " im messages.");
2219                     while (imCursor.moveToNext()) {
2220                         if (V) BluetoothMapUtils.printCursor(imCursor);
2221                         e = element(imCursor, fi, ap);
2222                         bmList.add(e);
2223                     }
2224                 }
2225             }
2226 
2227             /* Enable this if post sorting and segmenting needed */
2228             bmList.sort();
2229             bmList.segment(ap.getMaxListCount(), offsetNum);
2230             List<BluetoothMapMessageListingElement> list = bmList.getList();
2231             int listSize = list.size();
2232             Cursor tmpCursor = null;
2233             for(int x=0;x<listSize;x++){
2234                 BluetoothMapMessageListingElement ele = list.get(x);
2235                 /* If OBEX "GET" request header includes "ParameterMask" with 'Type' NOT set,
2236                  * then ele.getType() returns "null" even for a valid cursor.
2237                  * Avoid NullPointerException in equals() check when 'mType' value is "null" */
2238                 TYPE tmpType = ele.getType();
2239                 if (smsCursor!= null &&
2240                         ((TYPE.SMS_GSM).equals(tmpType) || (TYPE.SMS_CDMA).equals(tmpType))) {
2241                     tmpCursor = smsCursor;
2242                     fi.mMsgType = FilterInfo.TYPE_SMS;
2243                 } else if(mmsCursor != null && (TYPE.MMS).equals(tmpType)) {
2244                     tmpCursor = mmsCursor;
2245                     fi.mMsgType = FilterInfo.TYPE_MMS;
2246                 } else if(emailCursor != null && ((TYPE.EMAIL).equals(tmpType))) {
2247                     tmpCursor = emailCursor;
2248                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
2249                 } else if(imCursor != null && ((TYPE.IM).equals(tmpType))) {
2250                     tmpCursor = imCursor;
2251                     fi.mMsgType = FilterInfo.TYPE_IM;
2252                 }
2253                 if(tmpCursor != null){
2254                     tmpCursor.moveToPosition(ele.getCursorIndex());
2255                     setSenderAddressing(ele, tmpCursor, fi, ap);
2256                     setSenderName(ele, tmpCursor, fi, ap);
2257                     setRecipientAddressing(ele, tmpCursor, fi, ap);
2258                     setRecipientName(ele, tmpCursor, fi, ap);
2259                     setSubject(ele, tmpCursor, fi, ap);
2260                     setSize(ele, tmpCursor, fi, ap);
2261                     setText(ele, tmpCursor, fi, ap);
2262                     setPriority(ele, tmpCursor, fi, ap);
2263                     setSent(ele, tmpCursor, fi, ap);
2264                     setProtected(ele, tmpCursor, fi, ap);
2265                     setReceptionStatus(ele, tmpCursor, fi, ap);
2266                     setAttachment(ele, tmpCursor, fi, ap);
2267 
2268                     if(mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10 ){
2269                         setDeliveryStatus(ele, tmpCursor, fi, ap);
2270                         setThreadId(ele, tmpCursor, fi, ap);
2271                         setThreadName(ele, tmpCursor, fi, ap);
2272                         setFolderType(ele, tmpCursor, fi, ap);
2273                     }
2274                 }
2275             }
2276         } finally {
2277             if(emailCursor != null)emailCursor.close();
2278             if(smsCursor != null)smsCursor.close();
2279             if(mmsCursor != null)mmsCursor.close();
2280             if(imCursor != null)imCursor.close();
2281         }
2282 
2283 
2284         if(D)Log.d(TAG, "messagelisting end");
2285         return bmList;
2286     }
2287 
2288     /**
2289      * Get the size of the message listing
2290      * @param folder Must contain a valid folder string != null
2291      * @param ap Parameters specifying message content and filters
2292      * @return Integer equal to message listing size
2293      */
msgListingSize(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2294     public int msgListingSize(BluetoothMapFolderElement folderElement,
2295             BluetoothMapAppParams ap) {
2296         if (D) Log.d(TAG, "msgListingSize: folder = " + folderElement.getName());
2297         int cnt = 0;
2298 
2299         /* Cache some info used throughout filtering */
2300         FilterInfo fi = new FilterInfo();
2301         setFilterInfo(fi);
2302 
2303         if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
2304             fi.mMsgType = FilterInfo.TYPE_SMS;
2305             String where = setWhereFilter(folderElement, fi, ap);
2306             Cursor c = mResolver.query(Sms.CONTENT_URI,
2307                     SMS_PROJECTION, where, null, Sms.DATE + " DESC");
2308             try {
2309                 if (c != null) {
2310                     cnt = c.getCount();
2311                 }
2312             } finally {
2313                 if (c != null) c.close();
2314             }
2315         }
2316 
2317         if (mmsSelected(ap)  && folderElement.hasSmsMmsContent()) {
2318             fi.mMsgType = FilterInfo.TYPE_MMS;
2319             String where = setWhereFilter(folderElement, fi, ap);
2320             Cursor c = mResolver.query(Mms.CONTENT_URI,
2321                     MMS_PROJECTION, where, null, Mms.DATE + " DESC");
2322             try {
2323                 if (c != null) {
2324                     cnt += c.getCount();
2325                 }
2326             } finally {
2327                 if (c != null) c.close();
2328             }
2329         }
2330 
2331         if (emailSelected(ap) && folderElement.hasEmailContent()) {
2332             fi.mMsgType = FilterInfo.TYPE_EMAIL;
2333             String where = setWhereFilter(folderElement, fi, ap);
2334             if(!where.isEmpty()) {
2335                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
2336                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
2337                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
2338                 try {
2339                     if (c != null) {
2340                         cnt += c.getCount();
2341                     }
2342                 } finally {
2343                     if (c != null) c.close();
2344                 }
2345             }
2346         }
2347 
2348         if (imSelected(ap) && folderElement.hasImContent()) {
2349             fi.mMsgType = FilterInfo.TYPE_IM;
2350             String where = setWhereFilter(folderElement, fi, ap);
2351             if(!where.isEmpty()) {
2352                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
2353                 Cursor c = mResolver.query(contentUri,
2354                         BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
2355                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
2356                 try {
2357                     if (c != null) {
2358                         cnt += c.getCount();
2359                     }
2360                 } finally {
2361                     if (c != null) c.close();
2362                 }
2363             }
2364         }
2365 
2366         if (D) Log.d(TAG, "msgListingSize: size = " + cnt);
2367         return cnt;
2368     }
2369 
2370     /**
2371      * Return true if there are unread messages in the requested list of messages
2372      * @param folder folder where the message listing should come from
2373      * @param ap application parameter object
2374      * @return true if unread messages are in the list, else false
2375      */
msgListingHasUnread(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)2376     public boolean msgListingHasUnread(BluetoothMapFolderElement folderElement,
2377             BluetoothMapAppParams ap) {
2378         if (D) Log.d(TAG, "msgListingHasUnread: folder = " + folderElement.getName());
2379         int cnt = 0;
2380 
2381         /* Cache some info used throughout filtering */
2382         FilterInfo fi = new FilterInfo();
2383         setFilterInfo(fi);
2384 
2385        if (smsSelected(fi, ap)  && folderElement.hasSmsMmsContent()) {
2386             fi.mMsgType = FilterInfo.TYPE_SMS;
2387             String where = setWhereFilterFolderType(folderElement, fi);
2388             where += " AND " + Sms.READ + "=0 ";
2389             where += setWhereFilterPeriod(ap, fi);
2390             Cursor c = mResolver.query(Sms.CONTENT_URI,
2391                 SMS_PROJECTION, where, null, Sms.DATE + " DESC");
2392             try {
2393                 if (c != null) {
2394                     cnt = c.getCount();
2395                 }
2396             } finally {
2397                 if (c != null) c.close();
2398             }
2399         }
2400 
2401         if (mmsSelected(ap)  && folderElement.hasSmsMmsContent()) {
2402             fi.mMsgType = FilterInfo.TYPE_MMS;
2403             String where = setWhereFilterFolderType(folderElement, fi);
2404             where += " AND " + Mms.READ + "=0 ";
2405             where += setWhereFilterPeriod(ap, fi);
2406             Cursor c = mResolver.query(Mms.CONTENT_URI,
2407                 MMS_PROJECTION, where, null, Sms.DATE + " DESC");
2408             try {
2409                 if (c != null) {
2410                     cnt += c.getCount();
2411                 }
2412             } finally {
2413                 if (c != null) c.close();
2414             }
2415         }
2416 
2417 
2418         if (emailSelected(ap) && folderElement.getFolderId() != -1) {
2419             fi.mMsgType = FilterInfo.TYPE_EMAIL;
2420             String where = setWhereFilterFolderType(folderElement, fi);
2421             if(!where.isEmpty()) {
2422                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
2423                 where += setWhereFilterPeriod(ap, fi);
2424                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
2425                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
2426                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
2427                 try {
2428                     if (c != null) {
2429                         cnt += c.getCount();
2430                     }
2431                 } finally {
2432                     if (c != null) c.close();
2433                 }
2434             }
2435         }
2436 
2437         if (imSelected(ap) && folderElement.hasImContent()) {
2438             fi.mMsgType = FilterInfo.TYPE_IM;
2439             String where = setWhereFilter(folderElement, fi, ap);
2440             if(!where.isEmpty()) {
2441                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
2442                 where += setWhereFilterPeriod(ap, fi);
2443                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
2444                 Cursor c = mResolver.query(contentUri,
2445                         BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
2446                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
2447                 try {
2448                     if (c != null) {
2449                         cnt += c.getCount();
2450                     }
2451                 } finally {
2452                     if (c != null) c.close();
2453                 }
2454             }
2455         }
2456 
2457         if (D) Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt);
2458         return (cnt>0)?true:false;
2459     }
2460 
2461     /**
2462      * Build the conversation listing.
2463      * @param ap The Application Parameters
2464      * @param sizeOnly TRUE: don't populate the list members, only build the list to get the size.
2465      * @return
2466      */
convoListing(BluetoothMapAppParams ap, boolean sizeOnly)2467     public BluetoothMapConvoListing convoListing(BluetoothMapAppParams ap, boolean sizeOnly) {
2468 
2469         if (D) Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType() );
2470         BluetoothMapConvoListing convoList = new BluetoothMapConvoListing();
2471 
2472         /* We overwrite the parameter mask here if it is 0 or not present, as this
2473          * should cause all parameters to be included in the message list. */
2474         if(ap.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
2475                 ap.getConvoParameterMask() == 0) {
2476             ap.setConvoParameterMask(CONVO_PARAMETER_MASK_DEFAULT);
2477             if (D) Log.v(TAG, "convoListing(): appParameterMask is zero or not present, " +
2478                     "changing to default: " + ap.getConvoParameterMask());
2479         }
2480 
2481         /* Possible filters:
2482          *  - Recipient name (contacts DB) or id (for SMS/MMS this is the thread-id contact-id)
2483          *  - Activity start/begin
2484          *  - Read status
2485          *  - Thread_id
2486          * The strategy for SMS/MMS
2487          *   With no filter on name - use limit and offset.
2488          *   With a filter on name - build the complete list of conversations and create a filter
2489          *                           mechanism
2490          *
2491          * The strategy for IM:
2492          *   Join the conversation table with the contacts table in a way that makes it possible to
2493          *   get the data needed in a single query.
2494          *   Manually handle limit/offset
2495          * */
2496 
2497         /* Cache some info used throughout filtering */
2498         FilterInfo fi = new FilterInfo();
2499         setFilterInfo(fi);
2500         Cursor smsMmsCursor = null;
2501         Cursor imEmailCursor = null;
2502         int offsetNum;
2503         if(sizeOnly) {
2504             offsetNum = 0;
2505         } else {
2506             offsetNum = ap.getStartOffset();
2507         }
2508         // Inverse meaning - hence a 1 is include.
2509         int msgTypesInclude = ((~ap.getFilterMessageType())
2510                 & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK);
2511         int maxThreads = ap.getMaxListCount()+ap.getStartOffset();
2512 
2513 
2514         try {
2515             if (smsSelected(fi, ap) || mmsSelected(ap)) {
2516                 String limit = "";
2517                 if((sizeOnly == false) && (ap.getMaxListCount()>0) &&
2518                         (ap.getFilterRecipient()==null)){
2519                     /* We can only use limit if we do not have a contacts filter */
2520                     limit=" LIMIT " + maxThreads;
2521                 }
2522                 StringBuilder sortOrder = new StringBuilder(Threads.DATE + " DESC");
2523                 if((sizeOnly == false) &&
2524                         ((msgTypesInclude & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM |
2525                         BluetoothMapAppParams.FILTER_NO_SMS_CDMA) |
2526                         BluetoothMapAppParams.FILTER_NO_MMS) == 0)
2527                         && ap.getFilterRecipient() == null){
2528                     // SMS/MMS messages only and no recipient filter - use optimization.
2529                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
2530                     if(D) Log.d(TAG, "SMS Limit => "+limit);
2531                     offsetNum = 0;
2532                 }
2533                 StringBuilder selection = new StringBuilder(120); // This covers most cases
2534                 ArrayList<String> selectionArgs = new ArrayList<String>(12); // Covers all cases
2535                 selection.append("1=1 "); // just to simplify building the where-clause
2536                 setConvoWhereFilterSmsMms(selection, selectionArgs, fi, ap);
2537                 String[] args = null;
2538                 if(selectionArgs.size() > 0) {
2539                     args = new String[selectionArgs.size()];
2540                     selectionArgs.toArray(args);
2541                 }
2542                 Uri uri = Threads.CONTENT_URI.buildUpon()
2543                         .appendQueryParameter("simple", "true").build();
2544                 sortOrder.append(limit);
2545                 if(D) Log.d(TAG, "Query using selection: " + selection.toString() +
2546                         " - sortOrder: " + sortOrder.toString());
2547                 // TODO: Optimize: Reduce projection based on convo parameter mask
2548                 smsMmsCursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(),
2549                         args, sortOrder.toString());
2550                 if (smsMmsCursor != null) {
2551                     // store column index so we don't have to look them up anymore (optimization)
2552                     if(D) Log.d(TAG, "Found " + smsMmsCursor.getCount()
2553                             + " sms/mms conversations.");
2554                     BluetoothMapConvoListingElement convoElement = null;
2555                     smsMmsCursor.moveToPosition(-1);
2556                     if(ap.getFilterRecipient() == null) {
2557                         int count = 0;
2558                         // We have no Recipient filter, add contacts after the list is reduced
2559                         while (smsMmsCursor.moveToNext()) {
2560                             convoElement = createConvoElement(smsMmsCursor, fi, ap);
2561                             convoList.add(convoElement);
2562                             count++;
2563                             if(sizeOnly == false && count >= maxThreads) {
2564                                 break;
2565                             }
2566                         }
2567                     } else {
2568                         // We must be able to filter on recipient, add contacts now
2569                         SmsMmsContacts contacts = new SmsMmsContacts();
2570                         while (smsMmsCursor.moveToNext()) {
2571                             int count = 0;
2572                             convoElement = createConvoElement(smsMmsCursor, fi, ap);
2573                             String idsStr =
2574                                     smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
2575                             // Add elements only if we do find a contact - if not we cannot apply
2576                             // the filter, hence the item is irrelevant
2577                             // TODO: Perhaps the spec. should be changes to be able to search on
2578                             //       phone number as well?
2579                             if(addSmsMmsContacts(convoElement, contacts, idsStr,
2580                                     ap.getFilterRecipient(), ap)) {
2581                                 convoList.add(convoElement);
2582                                 if(sizeOnly == false && count >= maxThreads) {
2583                                     break;
2584                                 }
2585                             }
2586                         }
2587                     }
2588                 }
2589             }
2590 
2591             if (emailSelected(ap) || imSelected(ap)) {
2592                 int count = 0;
2593                 if(emailSelected(ap)) {
2594                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
2595                 } else if(imSelected(ap)) {
2596                     fi.mMsgType = FilterInfo.TYPE_IM;
2597                 }
2598                 if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
2599                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
2600 
2601                 contentUri = appendConvoListQueryParameters(ap, contentUri);
2602                 if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
2603                 // TODO: Optimize: Reduce projection based on convo parameter mask
2604                 imEmailCursor = mResolver.query(contentUri,
2605                         BluetoothMapContract.BT_CONVERSATION_PROJECTION,
2606                         null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
2607                         + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
2608                         + " ASC");
2609                 if (imEmailCursor != null) {
2610                     BluetoothMapConvoListingElement e = null;
2611                     // store column index so we don't have to look them up anymore (optimization)
2612                     // Here we rely on only a single account-based message type for each MAS.
2613                     fi.setEmailImConvoColumns(imEmailCursor);
2614                     boolean isValid = imEmailCursor.moveToNext();
2615                     if(D) Log.d(TAG, "Found " + imEmailCursor.getCount()
2616                             + " EMAIL/IM conversations. isValid = " + isValid);
2617                     while (isValid && ((sizeOnly == true) || (count < maxThreads))) {
2618                         long threadId = imEmailCursor.getLong(fi.mConvoColConvoId);
2619                         long nextThreadId;
2620                         count ++;
2621                         e = createConvoElement(imEmailCursor, fi, ap);
2622                         convoList.add(e);
2623 
2624                         do {
2625                             nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
2626                             if(V) Log.i(TAG, "  threadId = " + threadId + " newThreadId = " +
2627                                     nextThreadId);
2628                             // TODO: This seems rather inefficient in the case where we do not need
2629                             //       to reduce the list.
2630                         } while ((nextThreadId == threadId) &&
2631                                 (isValid = imEmailCursor.moveToNext() == true));
2632                     }
2633                 }
2634             }
2635 
2636             if(D) Log.d(TAG, "Done adding conversations - list size:" +
2637                     convoList.getCount());
2638 
2639             // If sizeOnly - we are all done here - return the list as is - no need to populate the
2640             // list.
2641             if(sizeOnly) {
2642                 return convoList;
2643             }
2644 
2645             /* Enable this if post sorting and segmenting needed */
2646             /* This is too early */
2647             convoList.sort();
2648             convoList.segment(ap.getMaxListCount(), offsetNum);
2649             List<BluetoothMapConvoListingElement> list = convoList.getList();
2650             int listSize = list.size();
2651             if(V) Log.i(TAG, "List Size:" + listSize);
2652             Cursor tmpCursor = null;
2653             SmsMmsContacts contacts = new SmsMmsContacts();
2654             for(int x=0;x<listSize;x++){
2655                 BluetoothMapConvoListingElement ele = list.get(x);
2656                 TYPE type = ele.getType();
2657                 switch(type) {
2658                 case SMS_CDMA:
2659                 case SMS_GSM:
2660                 case MMS: {
2661                     tmpCursor = null; // SMS/MMS needs special treatment
2662                     if(smsMmsCursor != null) {
2663                         populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts);
2664                     }
2665                     if(D) fi.mMsgType = FilterInfo.TYPE_IM;
2666                     break;
2667                 }
2668                 case EMAIL:
2669                     tmpCursor = imEmailCursor;
2670                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
2671                     break;
2672                 case IM:
2673                     tmpCursor = imEmailCursor;
2674                     fi.mMsgType = FilterInfo.TYPE_IM;
2675                     break;
2676                 default:
2677                     tmpCursor = null;
2678                     break;
2679                 }
2680 
2681                 if(D) Log.d(TAG, "Working on cursor of type " + fi.mMsgType);
2682 
2683                 if(tmpCursor != null){
2684                     populateImEmailConvoElement(ele, tmpCursor, ap, fi);
2685                 }else {
2686                     // No, it will be for SMS/MMS at the moment
2687                     if(D) Log.d(TAG, "tmpCursor is Null - something is wrong - or the message is" +
2688                             " of type SMS/MMS");
2689                 }
2690             }
2691         } finally {
2692             if(imEmailCursor != null)imEmailCursor.close();
2693             if(smsMmsCursor != null)smsMmsCursor.close();
2694             if(D)Log.d(TAG, "conversation end");
2695         }
2696         return convoList;
2697     }
2698 
2699 
2700     /**
2701      * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a
2702      * new ConvoListVersinoCounter in mSmsMmsConvoListVersion
2703      * @return
2704      */
2705     /* package */
refreshSmsMmsConvoVersions()2706     boolean refreshSmsMmsConvoVersions() {
2707         boolean listChangeDetected = false;
2708         Cursor cursor = null;
2709         Uri uri = Threads.CONTENT_URI.buildUpon()
2710                 .appendQueryParameter("simple", "true").build();
2711         cursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null,
2712                 null, Threads.DATE + " DESC");
2713         try {
2714             if (cursor != null) {
2715                 // store column index so we don't have to look them up anymore (optimization)
2716                 if(D) Log.d(TAG, "Found " + cursor.getCount()
2717                         + " sms/mms conversations.");
2718                 BluetoothMapConvoListingElement convoElement = null;
2719                 cursor.moveToPosition(-1);
2720                 synchronized (getSmsMmsConvoList()) {
2721                     int size = Math.max(getSmsMmsConvoList().size(), cursor.getCount());
2722                     HashMap<Long,BluetoothMapConvoListingElement> newList =
2723                             new HashMap<Long,BluetoothMapConvoListingElement>(size);
2724                     while (cursor.moveToNext()) {
2725                         // TODO: Extract to function, that can be called at listing, which returns
2726                         //       the versionCounter(existing or new).
2727                         boolean convoChanged = false;
2728                         Long id = cursor.getLong(MMS_SMS_THREAD_COL_ID);
2729                         convoElement = getSmsMmsConvoList().remove(id);
2730                         if(convoElement == null) {
2731                             // New conversation added
2732                             convoElement = new BluetoothMapConvoListingElement();
2733                             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
2734                             listChangeDetected = true;
2735                             convoElement.setVersionCounter(0);
2736                         }
2737                         // Currently we only need to compare name, last_activity and read_status, and
2738                         // name is not used for SMS/MMS.
2739                         // msg delete will be handled by update folderVersionCounter().
2740                         long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
2741                         boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
2742                                 true : false;
2743 
2744                         if(last_activity != convoElement.getLastActivity()) {
2745                             convoChanged = true;
2746                             convoElement.setLastActivity(last_activity);
2747                         }
2748 
2749                         if(read != convoElement.getReadBool()) {
2750                             convoChanged = true;
2751                             convoElement.setRead(read, false);
2752                         }
2753 
2754                         String idsStr = cursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
2755                         if(!idsStr.equals(convoElement.getSmsMmsContacts())) {
2756                             // This should not trigger a change in conversationVersionCounter only the
2757                             // ConvoListVersionCounter.
2758                             listChangeDetected = true;
2759                             convoElement.setSmsMmsContacts(idsStr);
2760                         }
2761 
2762                         if(convoChanged) {
2763                             listChangeDetected = true;
2764                             convoElement.incrementVersionCounter();
2765                         }
2766                         newList.put(id, convoElement);
2767                     }
2768                     // If we still have items on the old list, something was deleted
2769                     if(getSmsMmsConvoList().size() != 0) {
2770                         listChangeDetected = true;
2771                     }
2772                     setSmsMmsConvoList(newList);
2773                 }
2774 
2775                 if(listChangeDetected) {
2776                     mMasInstance.updateSmsMmsConvoListVersionCounter();
2777                 }
2778             }
2779         } finally {
2780             if(cursor != null) {
2781                 cursor.close();
2782             }
2783         }
2784         return listChangeDetected;
2785     }
2786 
2787     /**
2788      * Refreshes the entire list of SMS/MMS conversation version counters. Use it to generate a
2789      * new ConvoListVersinoCounter in mSmsMmsConvoListVersion
2790      * @return
2791      */
2792     /* package */
refreshImEmailConvoVersions()2793     boolean refreshImEmailConvoVersions() {
2794         boolean listChangeDetected = false;
2795         FilterInfo fi = new FilterInfo();
2796 
2797         Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
2798 
2799         if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
2800         Cursor imEmailCursor = mResolver.query(contentUri,
2801                 CONVO_VERSION_PROJECTION,
2802                 null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
2803                 + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
2804                 + " ASC");
2805         try {
2806             if (imEmailCursor != null) {
2807                 BluetoothMapConvoListingElement convoElement = null;
2808                 // store column index so we don't have to look them up anymore (optimization)
2809                 // Here we rely on only a single account-based message type for each MAS.
2810                 fi.setEmailImConvoColumns(imEmailCursor);
2811                 boolean isValid = imEmailCursor.moveToNext();
2812                 if(V) Log.d(TAG, "Found " + imEmailCursor.getCount()
2813                         + " EMAIL/IM conversations. isValid = " + isValid);
2814                 synchronized (getImEmailConvoList()) {
2815                     int size = Math.max(getImEmailConvoList().size(), imEmailCursor.getCount());
2816                     boolean convoChanged = false;
2817                     HashMap<Long,BluetoothMapConvoListingElement> newList =
2818                             new HashMap<Long,BluetoothMapConvoListingElement>(size);
2819                     while (isValid) {
2820                         long id = imEmailCursor.getLong(fi.mConvoColConvoId);
2821                         long nextThreadId;
2822                         convoElement = getImEmailConvoList().remove(id);
2823                         if(convoElement == null) {
2824                             // New conversation added
2825                             convoElement = new BluetoothMapConvoListingElement();
2826                             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
2827                             listChangeDetected = true;
2828                             convoElement.setVersionCounter(0);
2829                         }
2830                         String name = imEmailCursor.getString(fi.mConvoColName);
2831                         String summary = imEmailCursor.getString(fi.mConvoColSummary);
2832                         long last_activity = imEmailCursor.getLong(fi.mConvoColLastActivity);
2833                         boolean read = (imEmailCursor.getInt(fi.mConvoColRead) == 1) ?
2834                                 true : false;
2835 
2836                         if(last_activity != convoElement.getLastActivity()) {
2837                             convoChanged = true;
2838                             convoElement.setLastActivity(last_activity);
2839                         }
2840 
2841                         if(read != convoElement.getReadBool()) {
2842                             convoChanged = true;
2843                             convoElement.setRead(read, false);
2844                         }
2845 
2846                         if(name != null && !name.equals(convoElement.getName())) {
2847                             convoChanged = true;
2848                             convoElement.setName(name);
2849                         }
2850 
2851                         if(summary != null && !summary.equals(convoElement.getFullSummary())) {
2852                             convoChanged = true;
2853                             convoElement.setSummary(summary);
2854                         }
2855                         /* If the query returned one row for each contact, skip all the dublicates */
2856                         do {
2857                             nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
2858                             if(V) Log.i(TAG, "  threadId = " + id + " newThreadId = " +
2859                                     nextThreadId);
2860                         } while ((nextThreadId == id) &&
2861                                 (isValid = imEmailCursor.moveToNext() == true));
2862 
2863                         if(convoChanged) {
2864                             listChangeDetected = true;
2865                             convoElement.incrementVersionCounter();
2866                         }
2867                         newList.put(id, convoElement);
2868                     }
2869                     // If we still have items on the old list, something was deleted
2870                     if(getImEmailConvoList().size() != 0) {
2871                         listChangeDetected = true;
2872                     }
2873                     setImEmailConvoList(newList);
2874                 }
2875             }
2876         } finally {
2877             if(imEmailCursor != null) {
2878                 imEmailCursor.close();
2879             }
2880         }
2881 
2882         if(listChangeDetected) {
2883             mMasInstance.updateImEmailConvoListVersionCounter();
2884         }
2885         return listChangeDetected;
2886     }
2887 
2888     /**
2889      * Update the convoVersionCounter within the element passed as parameter.
2890      * This function has the side effect to update the ConvoListVersionCounter if needed.
2891      * This function ignores changes to contacts as this shall not change the convoVersionCounter,
2892      * only the convoListVersion counter, which will be updated upon request.
2893      * @param ele Element to update shall not be null.
2894      */
updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele)2895     private void updateSmsMmsConvoVersion(Cursor cursor, BluetoothMapConvoListingElement ele) {
2896         long id = ele.getCpConvoId();
2897         BluetoothMapConvoListingElement convoElement = getSmsMmsConvoList().get(id);
2898         boolean listChangeDetected = false;
2899         boolean convoChanged = false;
2900         if(convoElement == null) {
2901             // New conversation added
2902             convoElement = new BluetoothMapConvoListingElement();
2903             getSmsMmsConvoList().put(id, convoElement);
2904             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
2905             listChangeDetected = true;
2906             convoElement.setVersionCounter(0);
2907         }
2908         long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
2909         boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
2910                 true : false;
2911 
2912         if(last_activity != convoElement.getLastActivity()) {
2913             convoChanged = true;
2914             convoElement.setLastActivity(last_activity);
2915         }
2916 
2917         if(read != convoElement.getReadBool()) {
2918             convoChanged = true;
2919             convoElement.setRead(read, false);
2920         }
2921 
2922         if(convoChanged) {
2923             listChangeDetected = true;
2924             convoElement.incrementVersionCounter();
2925         }
2926         if(listChangeDetected) {
2927             mMasInstance.updateSmsMmsConvoListVersionCounter();
2928         }
2929         ele.setVersionCounter(convoElement.getVersionCounter());
2930     }
2931 
2932     /**
2933      * Update the convoVersionCounter within the element passed as parameter.
2934      * This function has the side effect to update the ConvoListVersionCounter if needed.
2935      * This function ignores changes to contacts as this shall not change the convoVersionCounter,
2936      * only the convoListVersion counter, which will be updated upon request.
2937      * @param ele Element to update shall not be null.
2938      */
updateImEmailConvoVersion(Cursor cursor, FilterInfo fi, BluetoothMapConvoListingElement ele)2939     private void updateImEmailConvoVersion(Cursor cursor, FilterInfo fi,
2940             BluetoothMapConvoListingElement ele) {
2941         long id = ele.getCpConvoId();
2942         BluetoothMapConvoListingElement convoElement = getImEmailConvoList().get(id);
2943         boolean listChangeDetected = false;
2944         boolean convoChanged = false;
2945         if(convoElement == null) {
2946             // New conversation added
2947             if(V) Log.d(TAG, "Added new conversation with ID = " + id);
2948             convoElement = new BluetoothMapConvoListingElement();
2949             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
2950             getImEmailConvoList().put(id, convoElement);
2951             listChangeDetected = true;
2952             convoElement.setVersionCounter(0);
2953         }
2954         String name = cursor.getString(fi.mConvoColName);
2955         long last_activity = cursor.getLong(fi.mConvoColLastActivity);
2956         boolean read = (cursor.getInt(fi.mConvoColRead) == 1) ?
2957                 true : false;
2958 
2959         if(last_activity != convoElement.getLastActivity()) {
2960             convoChanged = true;
2961             convoElement.setLastActivity(last_activity);
2962         }
2963 
2964         if(read != convoElement.getReadBool()) {
2965             convoChanged = true;
2966             convoElement.setRead(read, false);
2967         }
2968 
2969         if(name != null && !name.equals(convoElement.getName())) {
2970             convoChanged = true;
2971             convoElement.setName(name);
2972         }
2973 
2974         if(convoChanged) {
2975             listChangeDetected = true;
2976             if(V) Log.d(TAG, "conversation with ID = " + id + " changed");
2977             convoElement.incrementVersionCounter();
2978         }
2979         if(listChangeDetected) {
2980             mMasInstance.updateImEmailConvoListVersionCounter();
2981         }
2982         ele.setVersionCounter(convoElement.getVersionCounter());
2983     }
2984 
2985     /**
2986      * @param ele
2987      * @param smsMmsCursor
2988      * @param ap
2989      * @param contacts
2990      */
populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele, Cursor smsMmsCursor, BluetoothMapAppParams ap, SmsMmsContacts contacts)2991     private void populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele,
2992             Cursor smsMmsCursor, BluetoothMapAppParams ap,
2993             SmsMmsContacts contacts) {
2994         smsMmsCursor.moveToPosition(ele.getCursorIndex());
2995         // TODO: If we ever get beyond 31 bit, change to long
2996         int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
2997 
2998         // TODO: How to determine whether the convo-IDs can be used across message
2999         //       types?
3000         ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS,
3001                 smsMmsCursor.getLong(MMS_SMS_THREAD_COL_ID));
3002 
3003         boolean read = (smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
3004                 true : false;
3005         if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
3006             ele.setRead(read, true);
3007         } else {
3008             ele.setRead(read, false);
3009         }
3010 
3011         if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
3012             long timeStamp = smsMmsCursor.getLong(MMS_SMS_THREAD_COL_DATE);
3013             ele.setLastActivity(timeStamp);
3014         } else {
3015             // We need to delete the time stamp, if it was added for multi msg-type
3016             ele.setLastActivity(-1);
3017         }
3018 
3019         if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
3020             updateSmsMmsConvoVersion(smsMmsCursor, ele);
3021         }
3022 
3023         if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
3024             ele.setName(""); // We never have a thread name for SMS/MMS
3025         }
3026 
3027         if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
3028             String summary = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET);
3029             String cs = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET_CS);
3030             if(summary != null && cs != null && !cs.equals("UTF-8")) {
3031                 try {
3032                     // TODO: Not sure this is how to convert to UTF-8
3033                     summary = new String(summary.getBytes(cs),"UTF-8");
3034                 } catch (UnsupportedEncodingException e){/*Cannot happen*/}
3035             }
3036             ele.setSummary(summary);
3037         }
3038 
3039         if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
3040             if(ap.getFilterRecipient() == null) {
3041                 // Add contacts only if not already added
3042                 String idsStr =
3043                         smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
3044                 addSmsMmsContacts(ele, contacts, idsStr, null, ap);
3045             }
3046         }
3047     }
3048 
3049     /**
3050      * @param ele
3051      * @param tmpCursor
3052      * @param fi
3053      */
populateImEmailConvoElement( BluetoothMapConvoListingElement ele, Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi)3054     private void populateImEmailConvoElement( BluetoothMapConvoListingElement ele,
3055             Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi) {
3056         tmpCursor.moveToPosition(ele.getCursorIndex());
3057         // TODO: If we ever get beyond 31 bit, change to long
3058         int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
3059         long threadId = tmpCursor.getLong(fi.mConvoColConvoId);
3060 
3061         // Mandatory field
3062         ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, threadId);
3063 
3064         if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
3065             ele.setName(tmpCursor.getString(fi.mConvoColName));
3066         }
3067 
3068         boolean reportRead = false;
3069         if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
3070             reportRead = true;
3071         }
3072         ele.setRead(((1==tmpCursor.getInt(fi.mConvoColRead))?true:false), reportRead);
3073 
3074         long timestamp = tmpCursor.getLong(fi.mConvoColLastActivity);
3075         if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
3076             ele.setLastActivity(timestamp);
3077         } else {
3078             // We need to delete the time stamp, if it was added for multi msg-type
3079             ele.setLastActivity(-1);
3080         }
3081 
3082 
3083         if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
3084             updateImEmailConvoVersion(tmpCursor, fi, ele);
3085         }
3086         if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
3087             ele.setSummary(tmpCursor.getString(fi.mConvoColSummary));
3088         }
3089         // TODO: For optimization, we could avoid joining the contact and convo tables
3090         //       if we have no filter nor this bit is set.
3091         if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
3092             do {
3093                 BluetoothMapConvoContactElement c = new BluetoothMapConvoContactElement();
3094                 if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) {
3095                     c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid),0));
3096                 }
3097                 if((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) {
3098                     c.setChatState(tmpCursor.getInt(fi.mContactColChatState));
3099                 }
3100                 if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) {
3101                     c.setPresenceAvailability(tmpCursor.getInt(fi.mContactColPresenceState));
3102                 }
3103                 if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) {
3104                     c.setPresenceStatus(tmpCursor.getString(fi.mContactColPresenceText));
3105                 }
3106                 if((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) {
3107                     c.setPriority(tmpCursor.getInt(fi.mContactColPriority));
3108                 }
3109                 if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) {
3110                     c.setDisplayName(tmpCursor.getString(fi.mContactColNickname));
3111                 }
3112                 if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
3113                     c.setContactId(tmpCursor.getString(fi.mContactColContactUci));
3114                 }
3115                 if((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) {
3116                     c.setLastActivity(tmpCursor.getLong(fi.mContactColLastActive));
3117                 }
3118                 if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
3119                     c.setName(tmpCursor.getString(fi.mContactColName));
3120                 }
3121                 ele.addContact(c);
3122             } while (tmpCursor.moveToNext() == true
3123                     && tmpCursor.getLong(fi.mConvoColConvoId) == threadId);
3124         }
3125     }
3126 
3127     /**
3128      * Extract the ConvoList parameters from appParams and build the matching URI with
3129      * query parameters.
3130      * @param ap the appParams from the request
3131      * @param contentUri the URI to append parameters to
3132      * @return the new URI with the appended parameters (if any)
3133      */
appendConvoListQueryParameters(BluetoothMapAppParams ap, Uri contentUri)3134     private Uri appendConvoListQueryParameters(BluetoothMapAppParams ap,
3135             Uri contentUri) {
3136         Builder newUri = contentUri.buildUpon();
3137         String str = ap.getFilterRecipient();
3138         if(str != null) {
3139             str = str.trim();
3140             str = str.replace("*", "%");
3141             newUri.appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, str);
3142         }
3143         long time = ap.getFilterLastActivityBegin();
3144         if(time > 0) {
3145             newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN,
3146                     Long.toString(time));
3147         }
3148         time = ap.getFilterLastActivityEnd();
3149         if(time > 0) {
3150             newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_END,
3151                     Long.toString(time));
3152         }
3153         int readStatus = ap.getFilterReadStatus();
3154         if(readStatus > 0) {
3155             if(readStatus == 1) {
3156                 // Conversations with Unread messages only
3157                 newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
3158                         "false");
3159             }else if(readStatus == 2) {
3160                 // Conversations with all read messages only
3161                 newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
3162                         "true");
3163             }
3164             // if both are set it will be the same as requesting an empty list, but
3165             // as it makes no sense with such a structure in a bit mask, we treat
3166             // requesting both the same as no filtering.
3167         }
3168         long convoId = -1;
3169         if(ap.getFilterConvoId() != null) {
3170             convoId = ap.getFilterConvoId().getLeastSignificantBits();
3171         }
3172         if(convoId > 0) {
3173             newUri.appendQueryParameter(BluetoothMapContract.FILTER_THREAD_ID,
3174                     Long.toString(convoId));
3175         }
3176         return newUri.build();
3177     }
3178 
3179     /**
3180      * Procedure if we have a filter:
3181      *  - loop through all ids to examine if there is a match (this will build the cache)
3182      *  - If there is a match loop again to add all contacts.
3183      *
3184      * Procedure if we don't have a filter
3185      *  - Add all contacts
3186      *
3187      * @param convoElement
3188      * @param contacts
3189      * @param idsStr
3190      * @param recipientFilter
3191      * @return
3192      */
addSmsMmsContacts( BluetoothMapConvoListingElement convoElement, SmsMmsContacts contacts, String idsStr, String recipientFilter, BluetoothMapAppParams ap)3193     private boolean addSmsMmsContacts( BluetoothMapConvoListingElement convoElement,
3194             SmsMmsContacts contacts, String idsStr, String recipientFilter,
3195             BluetoothMapAppParams ap) {
3196         BluetoothMapConvoContactElement contactElement;
3197         int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
3198         boolean foundContact = false;
3199         String[] ids = idsStr.split(" ");
3200         long[] longIds = new long[ids.length];
3201         if(recipientFilter != null) {
3202             recipientFilter = recipientFilter.trim();
3203         }
3204 
3205         for (int i = 0; i < ids.length; i++) {
3206             long longId;
3207             try {
3208                 longId = Long.parseLong(ids[i]);
3209                 longIds[i] = longId;
3210                 if(recipientFilter == null) {
3211                     // If there is not filter, all we need to do is to parse the ids
3212                     foundContact = true;
3213                     continue;
3214                 }
3215                 String addr = contacts.getPhoneNumber(mResolver, longId);
3216                 if(addr == null) {
3217                     // This can only happen if all messages from a contact is deleted while
3218                     // performing the query.
3219                     continue;
3220                 }
3221                 MapContact contact =
3222                         contacts.getContactNameFromPhone(addr, mResolver, recipientFilter);
3223                 if(D) {
3224                     Log.d(TAG, "  id " + longId + ": " + addr);
3225                     if(contact != null) {
3226                         Log.d(TAG,"  contact name: " + contact.getName() + "  X-BT-UID: "
3227                                 + contact.getXBtUid());
3228                     }
3229                 }
3230                 if(contact == null) {
3231                     continue;
3232                 }
3233                 foundContact = true;
3234             } catch (NumberFormatException ex) {
3235                 // skip this id
3236                 continue;
3237             }
3238         }
3239 
3240         if(foundContact == true) {
3241             foundContact = false;
3242             for (long id : longIds) {
3243                 String addr = contacts.getPhoneNumber(mResolver, id);
3244                 if(addr == null) {
3245                     // This can only happen if all messages from a contact is deleted while
3246                     // performing the query.
3247                     continue;
3248                 }
3249                 foundContact = true;
3250                 MapContact contact = contacts.getContactNameFromPhone(addr, mResolver);
3251 
3252                 if(contact == null) {
3253                     // We do not have a contact, we need to manually add one
3254                     contactElement = new BluetoothMapConvoContactElement();
3255                     if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
3256                         contactElement.setName(addr); // Use the phone number as name
3257                     }
3258                     if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
3259                         contactElement.setContactId(addr);
3260                     }
3261                 } else {
3262                     contactElement = BluetoothMapConvoContactElement
3263                             .createFromMapContact(contact, addr);
3264                     // Remove the parameters not to be reported
3265                     if((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) {
3266                         contactElement.setContactId(null);
3267                     }
3268                     if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) {
3269                         contactElement.setBtUid(null);
3270                     }
3271                     if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) {
3272                         contactElement.setDisplayName(null);
3273                     }
3274                 }
3275                 convoElement.addContact(contactElement);
3276             }
3277         }
3278         return foundContact;
3279     }
3280 
3281     /**
3282      * Get the folder name of an SMS message or MMS message.
3283      * @param c the cursor pointing at the message
3284      * @return the folder name.
3285      */
getFolderName(int type, int threadId)3286     private String getFolderName(int type, int threadId) {
3287 
3288         if(threadId == -1)
3289             return BluetoothMapContract.FOLDER_NAME_DELETED;
3290 
3291         switch(type) {
3292         case 1:
3293             return BluetoothMapContract.FOLDER_NAME_INBOX;
3294         case 2:
3295             return BluetoothMapContract.FOLDER_NAME_SENT;
3296         case 3:
3297             return BluetoothMapContract.FOLDER_NAME_DRAFT;
3298         case 4: // Just name outbox, failed and queued "outbox"
3299         case 5:
3300         case 6:
3301             return BluetoothMapContract.FOLDER_NAME_OUTBOX;
3302         }
3303         return "";
3304     }
3305 
getMessage(String handle, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement, String version)3306     public byte[] getMessage(String handle, BluetoothMapAppParams appParams,
3307             BluetoothMapFolderElement folderElement, String version)
3308             throws UnsupportedEncodingException{
3309         TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
3310         mMessageVersion = version;
3311         long id = BluetoothMapUtils.getCpHandle(handle);
3312         if(appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) {
3313             throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as" +
3314                                                " we always return the full message.");
3315         }
3316         switch(type) {
3317         case SMS_GSM:
3318         case SMS_CDMA:
3319             return getSmsMessage(id, appParams.getCharset());
3320         case MMS:
3321             return getMmsMessage(id, appParams);
3322         case EMAIL:
3323             return getEmailMessage(id, appParams, folderElement);
3324         case IM:
3325             return getIMMessage(id, appParams, folderElement);
3326         }
3327         throw new IllegalArgumentException("Invalid message handle.");
3328     }
3329 
setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming)3330     private String setVCardFromPhoneNumber(BluetoothMapbMessage message,
3331             String phone, boolean incoming) {
3332         String contactId = null, contactName = null;
3333         String[] phoneNumbers = new String[1];
3334         //Handle possible exception for empty phone address
3335         if (TextUtils.isEmpty(phone)) {
3336             return contactName;
3337         }
3338         //
3339         // Use only actual phone number, because the MCE cannot know which
3340         // number the message is from.
3341         //
3342         phoneNumbers[0] = phone;
3343         String[] emailAddresses = null;
3344         Cursor p;
3345 
3346         Uri uri = Uri
3347                 .withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
3348                 Uri.encode(phone));
3349 
3350         String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
3351         String selection = Contacts.IN_VISIBLE_GROUP + "=1";
3352         String orderBy = Contacts._ID + " ASC";
3353 
3354         // Get the contact _ID and name
3355         p = mResolver.query(uri, projection, selection, null, orderBy);
3356         try {
3357             if (p != null && p.moveToFirst()) {
3358                 contactId = p.getString(p.getColumnIndex(Contacts._ID));
3359                 contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
3360             }
3361         } finally {
3362             close(p);
3363         }
3364         // Bail out if we are unable to find a contact, based on the phone number
3365         if (contactId != null) {
3366             Cursor q = null;
3367             // Fetch the contact e-mail addresses
3368             try {
3369                 q = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
3370                         ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
3371                         new String[]{contactId},
3372                         null);
3373                 if (q != null && q.moveToFirst()) {
3374                     int i = 0;
3375                     emailAddresses = new String[q.getCount()];
3376                     do {
3377                         String emailAddress = q.getString(q.getColumnIndex(
3378                                 ContactsContract.CommonDataKinds.Email.ADDRESS));
3379                         emailAddresses[i++] = emailAddress;
3380                     } while (q != null && q.moveToNext());
3381                 }
3382             } finally {
3383                 close(q);
3384             }
3385         }
3386 
3387         if (incoming == true) {
3388             if(V) Log.d(TAG, "Adding originator for phone:" + phone);
3389             // Use version 3.0 as we only have a formatted name
3390             message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses,null,null);
3391         } else {
3392             if(V) Log.d(TAG, "Adding recipient for phone:" + phone);
3393             // Use version 3.0 as we only have a formatted name
3394             message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses,null,null);
3395         }
3396         return contactName;
3397     }
3398 
3399     public static final int MAP_MESSAGE_CHARSET_NATIVE = 0;
3400     public static final int MAP_MESSAGE_CHARSET_UTF8 = 1;
3401 
getSmsMessage(long id, int charset)3402     public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException{
3403         int type, threadId;
3404         long time = -1;
3405         String msgBody;
3406         BluetoothMapbMessageSms message = new BluetoothMapbMessageSms();
3407         TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
3408 
3409         Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null);
3410         if (c == null || !c.moveToFirst()) {
3411             throw new IllegalArgumentException("SMS handle not found");
3412         }
3413 
3414         try{
3415             if(c != null && c.moveToFirst())
3416             {
3417                 if(V) Log.v(TAG,"c.count: " + c.getCount());
3418 
3419                 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
3420                     message.setType(TYPE.SMS_GSM);
3421                 } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
3422                     message.setType(TYPE.SMS_CDMA);
3423                 }
3424                 message.setVersionString(mMessageVersion);
3425                 String read = c.getString(c.getColumnIndex(Sms.READ));
3426                 if (read.equalsIgnoreCase("1"))
3427                     message.setStatus(true);
3428                 else
3429                     message.setStatus(false);
3430 
3431                 type = c.getInt(c.getColumnIndex(Sms.TYPE));
3432                 threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
3433                 message.setFolder(getFolderName(type, threadId));
3434 
3435                 msgBody = c.getString(c.getColumnIndex(Sms.BODY));
3436 
3437                 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
3438                 if ((phone == null) && type == Sms.MESSAGE_TYPE_DRAFT) {
3439                     //Fetch address for Drafts folder from "canonical_address" table
3440                     phone  = getCanonicalAddressSms(mResolver, threadId);
3441                 }
3442                 time = c.getLong(c.getColumnIndex(Sms.DATE));
3443                 if(type == 1) // Inbox message needs to set the vCard as originator
3444                     setVCardFromPhoneNumber(message, phone, true);
3445                 else          // Other messages sets the vCard as the recipient
3446                     setVCardFromPhoneNumber(message, phone, false);
3447 
3448                 if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
3449                     if(type == 1) //Inbox
3450                         message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody,
3451                                     phone, time));
3452                     else
3453                         message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
3454                 } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
3455                     message.setSmsBody(msgBody);
3456                 }
3457                 return message.encode();
3458             }
3459         } finally {
3460             if (c != null) c.close();
3461         }
3462 
3463         return message.encode();
3464     }
3465 
extractMmsAddresses(long id, BluetoothMapbMessageMime message)3466     private void extractMmsAddresses(long id, BluetoothMapbMessageMime message) {
3467         final String[] projection = null;
3468         String selection = new String(Mms.Addr.MSG_ID + "=" + id);
3469         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
3470         Uri uriAddress = Uri.parse(uriStr);
3471         String contactName = null;
3472 
3473         Cursor c = mResolver.query( uriAddress, projection, selection, null, null);
3474         try {
3475             if (c.moveToFirst()) {
3476                 do {
3477                     String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
3478                     if(address.equals(INSERT_ADDRES_TOKEN))
3479                         continue;
3480                     Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
3481                     switch(type) {
3482                     case MMS_FROM:
3483                         contactName = setVCardFromPhoneNumber(message, address, true);
3484                         message.addFrom(contactName, address);
3485                         break;
3486                     case MMS_TO:
3487                         contactName = setVCardFromPhoneNumber(message, address, false);
3488                         message.addTo(contactName, address);
3489                         break;
3490                     case MMS_CC:
3491                         contactName = setVCardFromPhoneNumber(message, address, false);
3492                         message.addCc(contactName, address);
3493                         break;
3494                     case MMS_BCC:
3495                         contactName = setVCardFromPhoneNumber(message, address, false);
3496                         message.addBcc(contactName, address);
3497                         break;
3498                     default:
3499                         break;
3500                     }
3501                 } while(c.moveToNext());
3502             }
3503         } finally {
3504             if (c != null) c.close();
3505         }
3506     }
3507 
3508 
3509     /**
3510      * Read out a mime data part and return the data in a byte array.
3511      * @param contentPartUri TODO
3512      * @param partid the content provider id of the Mime Part.
3513      * @return
3514      */
readRawDataPart(Uri contentPartUri, long partid)3515     private byte[] readRawDataPart(Uri contentPartUri, long partid) {
3516         String uriStr = new String(contentPartUri+"/"+ partid);
3517         Uri uriAddress = Uri.parse(uriStr);
3518         InputStream is = null;
3519         ByteArrayOutputStream os = new ByteArrayOutputStream();
3520         int bufferSize = 8192;
3521         byte[] buffer = new byte[bufferSize];
3522         byte[] retVal = null;
3523 
3524         try {
3525             is = mResolver.openInputStream(uriAddress);
3526             int len = 0;
3527             while ((len = is.read(buffer)) != -1) {
3528               os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
3529             }
3530             retVal = os.toByteArray();
3531         } catch (IOException e) {
3532             // do nothing for now
3533             Log.w(TAG,"Error reading part data",e);
3534         } finally {
3535             close(os);
3536             close(is);
3537         }
3538         return retVal;
3539     }
3540 
3541     /**
3542      * Read out the mms parts and update the bMessage object provided i {@linkplain message}
3543      * @param id the content provider ID of the message
3544      * @param message the bMessage object to add the information to
3545      */
extractMmsParts(long id, BluetoothMapbMessageMime message)3546     private void extractMmsParts(long id, BluetoothMapbMessageMime message)
3547     {
3548         /* Handling of filtering out non-text parts for exclude
3549          * attachments is handled within the bMessage object. */
3550         final String[] projection = null;
3551         String selection = new String(Mms.Part.MSG_ID + "=" + id);
3552         String uriStr = new String(Mms.CONTENT_URI + "/"+ id + "/part");
3553         Uri uriAddress = Uri.parse(uriStr);
3554         BluetoothMapbMessageMime.MimePart part;
3555         Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
3556         try {
3557             if (c.moveToFirst()) {
3558                 do {
3559                     Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
3560                     String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE));
3561                     String name = c.getString(c.getColumnIndex(Mms.Part.NAME));
3562                     String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET));
3563                     String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME));
3564                     String text = c.getString(c.getColumnIndex(Mms.Part.TEXT));
3565                     Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA));
3566                     String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID));
3567                     String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION));
3568                     String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION));
3569 
3570                     if(V) Log.d(TAG, "     _id : " + partId +
3571                             "\n     ct : " + contentType +
3572                             "\n     partname : " + name +
3573                             "\n     charset : " + charset +
3574                             "\n     filename : " + filename +
3575                             "\n     text : " + text +
3576                             "\n     fd : " + fd +
3577                             "\n     cid : " + cid +
3578                             "\n     cl : " + cl +
3579                             "\n     cdisp : " + cdisp);
3580 
3581                     part = message.addMimePart();
3582                     part.mContentType = contentType;
3583                     part.mPartName = name;
3584                     part.mContentId = cid;
3585                     part.mContentLocation = cl;
3586                     part.mContentDisposition = cdisp;
3587 
3588                     try {
3589                         if(text != null) {
3590                             part.mData = text.getBytes("UTF-8");
3591                             part.mCharsetName = "utf-8";
3592                         } else {
3593                             part.mData =
3594                                     readRawDataPart(Uri.parse(Mms.CONTENT_URI+"/part"), partId);
3595                             if(charset != null) {
3596                                 part.mCharsetName =
3597                                         CharacterSets.getMimeName(Integer.parseInt(charset));
3598                             }
3599                         }
3600                     } catch (NumberFormatException e) {
3601                         Log.d(TAG,"extractMmsParts",e);
3602                         part.mData = null;
3603                         part.mCharsetName = null;
3604                     } catch (UnsupportedEncodingException e) {
3605                         Log.d(TAG,"extractMmsParts",e);
3606                         part.mData = null;
3607                         part.mCharsetName = null;
3608                     } finally {
3609                     }
3610                     part.mFileName = filename;
3611                 } while(c.moveToNext());
3612                 message.updateCharset();
3613             }
3614 
3615         } finally {
3616             if(c != null) c.close();
3617         }
3618     }
3619     /**
3620      * Read out the mms parts and update the bMessage object provided i {@linkplain message}
3621      * @param id the content provider ID of the message
3622      * @param message the bMessage object to add the information to
3623      */
extractIMParts(long id, BluetoothMapbMessageMime message)3624     private void extractIMParts(long id, BluetoothMapbMessageMime message)
3625     {
3626         /* Handling of filtering out non-text parts for exclude
3627          * attachments is handled within the bMessage object. */
3628         final String[] projection = null;
3629         String selection = new String(BluetoothMapContract.MessageColumns._ID + "=" + id);
3630         String uriStr = new String(mBaseUri
3631                                          + BluetoothMapContract.TABLE_MESSAGE + "/"+ id + "/part");
3632         Uri uriAddress = Uri.parse(uriStr);
3633         BluetoothMapbMessageMime.MimePart part;
3634         Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
3635         try{
3636             if (c.moveToFirst()) {
3637                 do {
3638                     Long partId = c.getLong(
3639                                   c.getColumnIndex(BluetoothMapContract.MessagePartColumns._ID));
3640                     String charset = c.getString(
3641                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CHARSET));
3642                     String filename = c.getString(
3643                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.FILENAME));
3644                     String text = c.getString(
3645                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.TEXT));
3646                     String body = c.getString(
3647                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.RAW_DATA));
3648                     String cid = c.getString(
3649                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CONTENT_ID));
3650 
3651                     if(V) Log.d(TAG, "     _id : " + partId +
3652                             "\n     charset : " + charset +
3653                             "\n     filename : " + filename +
3654                             "\n     text : " + text +
3655                             "\n     cid : " + cid);
3656 
3657                     part = message.addMimePart();
3658                     part.mContentId = cid;
3659                     try {
3660                         if(text.equalsIgnoreCase("yes")) {
3661                             part.mData = body.getBytes("UTF-8");
3662                             part.mCharsetName = "utf-8";
3663                         } else {
3664                             part.mData = readRawDataPart(Uri.parse(mBaseUri
3665                                              + BluetoothMapContract.TABLE_MESSAGE_PART) , partId);
3666                             if(charset != null)
3667                                 part.mCharsetName = CharacterSets.getMimeName(
3668                                                                         Integer.parseInt(charset));
3669                         }
3670                     } catch (NumberFormatException e) {
3671                         Log.d(TAG,"extractIMParts",e);
3672                         part.mData = null;
3673                         part.mCharsetName = null;
3674                     } catch (UnsupportedEncodingException e) {
3675                         Log.d(TAG,"extractIMParts",e);
3676                         part.mData = null;
3677                         part.mCharsetName = null;
3678                     } finally {
3679                     }
3680                     part.mFileName = filename;
3681                 } while(c.moveToNext());
3682             }
3683         } finally {
3684             if(c != null) c.close();
3685         }
3686 
3687         message.updateCharset();
3688     }
3689 
3690     /**
3691      *
3692      * @param id the content provider id for the message to fetch.
3693      * @param appParams The application parameter object received from the client.
3694      * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
3695      * @throws UnsupportedEncodingException if UTF-8 is not supported,
3696      * which is guaranteed to be supported on an android device
3697      */
getMmsMessage(long id,BluetoothMapAppParams appParams)3698     public byte[] getMmsMessage(long id,BluetoothMapAppParams appParams)
3699                                                         throws UnsupportedEncodingException {
3700         int msgBox, threadId;
3701         if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
3702             throw new IllegalArgumentException("MMS charset native not allowed for MMS"
3703                                                                             +" - must be utf-8");
3704 
3705         BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
3706         Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null);
3707         try {
3708             if(c != null && c.moveToFirst())
3709             {
3710                 message.setType(TYPE.MMS);
3711                 message.setVersionString(mMessageVersion);
3712 
3713                 // The MMS info:
3714                 String read = c.getString(c.getColumnIndex(Mms.READ));
3715                 if (read.equalsIgnoreCase("1"))
3716                     message.setStatus(true);
3717                 else
3718                     message.setStatus(false);
3719 
3720                 msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
3721                 threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
3722                 message.setFolder(getFolderName(msgBox, threadId));
3723                 message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT)));
3724                 message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
3725                 message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
3726                 message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
3727                 message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true);
3728                 message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
3729                 // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
3730                 // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
3731 
3732                 // The parts
3733                 extractMmsParts(id, message);
3734 
3735                 // The addresses
3736                 extractMmsAddresses(id, message);
3737 
3738 
3739                 return message.encode();
3740             }
3741         } finally {
3742             if (c != null) c.close();
3743         }
3744 
3745         return message.encode();
3746     }
3747 
3748     /**
3749     *
3750     * @param id the content provider id for the message to fetch.
3751     * @param appParams The application parameter object received from the client.
3752     * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
3753     * @throws UnsupportedEncodingException if UTF-8 is not supported,
3754     * which is guaranteed to be supported on an android device
3755     */
getEmailMessage(long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement currentFolder)3756    public byte[] getEmailMessage(long id, BluetoothMapAppParams appParams,
3757            BluetoothMapFolderElement currentFolder) throws UnsupportedEncodingException {
3758        // Log print out of application parameters set
3759        if(D && appParams != null) {
3760            Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() +
3761                    ", Charset = " + appParams.getCharset() +
3762                    ", FractionRequest = " + appParams.getFractionRequest());
3763        }
3764 
3765        // Throw exception if requester NATIVE charset for Email
3766        // Exception is caught by MapObexServer sendGetMessageResp
3767        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
3768            throw new IllegalArgumentException("EMAIL charset not UTF-8");
3769 
3770        BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail();
3771        Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
3772        Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = "
3773                + id, null, null);
3774        try {
3775            if(c != null && c.moveToFirst())
3776            {
3777                BluetoothMapFolderElement folderElement;
3778                FileInputStream is = null;
3779                ParcelFileDescriptor fd = null;
3780                try {
3781                    // Handle fraction requests
3782                    int fractionRequest = appParams.getFractionRequest();
3783                    if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
3784                        // Fraction requested
3785                        if(V) {
3786                            String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
3787                            Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
3788                                    +  " - send compete message" );
3789                        }
3790                        // Check if message is complete and if not - request message from server
3791                        if (c.getString(c.getColumnIndex(
3792                                BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase(
3793                                        BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false)  {
3794                            // TODO: request message from server
3795                            Log.w(TAG, "getEmailMessage - receptionState not COMPLETE -  Not Implemented!" );
3796                        }
3797                    }
3798                    // Set read status:
3799                    String read = c.getString(
3800                                         c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
3801                    if (read != null && read.equalsIgnoreCase("1"))
3802                        message.setStatus(true);
3803                    else
3804                        message.setStatus(false);
3805 
3806                    // Set message type:
3807                    message.setType(TYPE.EMAIL);
3808                    message.setVersionString(mMessageVersion);
3809                    // Set folder:
3810                    long folderId = c.getLong(
3811                                        c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
3812                    folderElement = currentFolder.getFolderById(folderId);
3813                    message.setCompleteFolder(folderElement.getFullPath());
3814 
3815                    // Set recipient:
3816                    String nameEmail = c.getString(
3817                                        c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
3818                    Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
3819                    if (tokens.length != 0) {
3820                        if(D) Log.d(TAG, "Recipient count= " + tokens.length);
3821                        int i = 0;
3822                        while (i < tokens.length) {
3823                            if(V) Log.d(TAG, "Recipient = " + tokens[i].toString());
3824                            String[] emails = new String[1];
3825                            emails[0] = tokens[i].getAddress();
3826                            String name = tokens[i].getName();
3827                            message.addRecipient(name, name, null, emails, null, null);
3828                            i++;
3829                        }
3830                    }
3831 
3832                    // Set originator:
3833                    nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
3834                    tokens = Rfc822Tokenizer.tokenize(nameEmail);
3835                    if (tokens.length != 0) {
3836                        if(D) Log.d(TAG, "Originator count= " + tokens.length);
3837                        int i = 0;
3838                        while (i < tokens.length) {
3839                            if(V) Log.d(TAG, "Originator = " + tokens[i].toString());
3840                            String[] emails = new String[1];
3841                            emails[0] = tokens[i].getAddress();
3842                            String name = tokens[i].getName();
3843                            message.addOriginator(name, name, null, emails, null, null);
3844                            i++;
3845                        }
3846                    }
3847                } finally {
3848                    if(c != null) c.close();
3849                }
3850                // Find out if we get attachments
3851                String attStr = (appParams.getAttachment() == 0) ?
3852                                            "/" +  BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
3853                Uri uri = Uri.parse(contentUri + "/" + id + attStr);
3854 
3855                // Get email message body content
3856                int count = 0;
3857                try {
3858                    fd = mResolver.openFileDescriptor(uri, "r");
3859                    is = new FileInputStream(fd.getFileDescriptor());
3860                    StringBuilder email = new StringBuilder("");
3861                    byte[] buffer = new byte[1024];
3862                    while((count = is.read(buffer)) != -1) {
3863                        // TODO: Handle breaks within a UTF8 character
3864                        email.append(new String(buffer,0,count));
3865                        if(V) Log.d(TAG, "Email part = "
3866                                          + new String(buffer,0,count)
3867                                          + " count=" + count);
3868                    }
3869                    // Set email message body:
3870                    message.setEmailBody(email.toString());
3871                } catch (FileNotFoundException e) {
3872                    Log.w(TAG, e);
3873                } catch (NullPointerException e) {
3874                    Log.w(TAG, e);
3875                } catch (IOException e) {
3876                    Log.w(TAG, e);
3877                } finally {
3878                    try {
3879                        if(is != null) is.close();
3880                    } catch (IOException e) {}
3881                    try {
3882                        if(fd != null) fd.close();
3883                    } catch (IOException e) {}
3884                }
3885                return message.encode();
3886            }
3887        } finally {
3888            if (c != null) c.close();
3889        }
3890        throw new IllegalArgumentException("EMAIL handle not found");
3891    }
3892    /**
3893    *
3894    * @param id the content provider id for the message to fetch.
3895    * @param appParams The application parameter object received from the client.
3896    * @return a byte[] containing the UTF-8 encoded bMessage to send to the client.
3897    * @throws UnsupportedEncodingException if UTF-8 is not supported,
3898    * which is guaranteed to be supported on an android device
3899    */
3900 
3901    /**
3902    *
3903    * @param id the content provider id for the message to fetch.
3904    * @param appParams The application parameter object received from the client.
3905    * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
3906    * @throws UnsupportedEncodingException if UTF-8 is not supported,
3907    * which is guaranteed to be supported on an android device
3908    */
getIMMessage(long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement)3909    public byte[] getIMMessage(long id,
3910            BluetoothMapAppParams appParams,
3911            BluetoothMapFolderElement folderElement)
3912                    throws UnsupportedEncodingException {
3913        long threadId, folderId;
3914 
3915        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
3916            throw new IllegalArgumentException(
3917                    "IM charset native not allowed for IM - must be utf-8");
3918 
3919        BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
3920        Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
3921        Cursor c = mResolver.query(contentUri,
3922                BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
3923        Cursor contacts = null;
3924        try {
3925            if(c != null && c.moveToFirst()) {
3926                message.setType(TYPE.IM);
3927                message.setVersionString(mMessageVersion);
3928 
3929                // The IM message info:
3930                int read =
3931                        c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
3932                if (read == 1)
3933                    message.setStatus(true);
3934                else
3935                    message.setStatus(false);
3936 
3937                threadId =
3938                        c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID));
3939                folderId =
3940                        c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
3941                folderElement = folderElement.getFolderById(folderId);
3942                message.setCompleteFolder(folderElement.getFullPath());
3943                message.setSubject(c.getString(
3944                        c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT)));
3945                message.setMessageId(c.getString(
3946                        c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)));
3947                message.setDate(c.getLong(
3948                        c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE)));
3949                message.setTextOnly(c.getInt(c.getColumnIndex(
3950                        BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE)) != 0 ? false : true);
3951 
3952                message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
3953 
3954                // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
3955                // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
3956 
3957                // The parts
3958 
3959                //FIXME use the parts when ready - until then use the body column for text-only
3960                //  extractIMParts(id, message);
3961                //FIXME next few lines are temporary code
3962                MimePart part = message.addMimePart();
3963                part.mData = c.getString((c.getColumnIndex(
3964                        BluetoothMapContract.MessageColumns.BODY))).getBytes("UTF-8");
3965                part.mCharsetName = "utf-8";
3966                part.mContentId = "0";
3967                part.mContentType = "text/plain";
3968                message.updateCharset();
3969                // FIXME end temp code
3970 
3971                Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
3972                contacts = mResolver.query(contactsUri,
3973                        BluetoothMapContract.BT_CONTACT_PROJECTION,
3974                        BluetoothMapContract.ConvoContactColumns.CONVO_ID
3975                        + " = " + threadId, null, null);
3976                // TODO this will not work for group-chats
3977                if(contacts != null && contacts.moveToFirst()){
3978                    String name = contacts.getString(contacts.getColumnIndex(
3979                            BluetoothMapContract.ConvoContactColumns.NAME));
3980                    String btUid[] = new String[1];
3981                    btUid[0]= contacts.getString(contacts.getColumnIndex(
3982                            BluetoothMapContract.ConvoContactColumns.X_BT_UID));
3983                    String nickname = contacts.getString(contacts.getColumnIndex(
3984                            BluetoothMapContract.ConvoContactColumns.NICKNAME));
3985                    String btUci[] = new String[1];
3986                    String btOwnUci[] = new String[1];
3987                    btOwnUci[0] = mAccount.getUciFull();
3988                    btUci[0] = contacts.getString(contacts.getColumnIndex(
3989                            BluetoothMapContract.ConvoContactColumns.UCI));
3990                    if(folderId == BluetoothMapContract.FOLDER_ID_SENT
3991                            || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) {
3992                        message.addRecipient(nickname,name,null, null, btUid, btUci);
3993                        message.addOriginator(null, btOwnUci);
3994 
3995                    }else {
3996                        message.addOriginator(nickname,name,null, null, btUid, btUci);
3997                        message.addRecipient(null, btOwnUci);
3998 
3999                    }
4000                }
4001                return message.encode();
4002            }
4003        } finally {
4004            if(c != null) c.close();
4005            if(contacts != null) contacts.close();
4006        }
4007 
4008        throw new IllegalArgumentException("IM handle not found");
4009    }
4010 
setRemoteFeatureMask(int featureMask)4011    public void setRemoteFeatureMask(int featureMask){
4012        this.mRemoteFeatureMask = featureMask;
4013        if(V) Log.d(TAG, "setRemoteFeatureMask");
4014        if((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
4015                == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
4016            if(V) Log.d(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11");
4017            this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
4018        }
4019    }
4020 
getRemoteFeatureMask()4021    public int getRemoteFeatureMask(){
4022        return this.mRemoteFeatureMask;
4023    }
4024 
getSmsMmsConvoList()4025     HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
4026         return mMasInstance.getSmsMmsConvoList();
4027     }
4028 
setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList)4029     void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
4030         mMasInstance.setSmsMmsConvoList(smsMmsConvoList);
4031     }
4032 
getImEmailConvoList()4033     HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
4034         return mMasInstance.getImEmailConvoList();
4035     }
4036 
setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList)4037     void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
4038         mMasInstance.setImEmailConvoList(imEmailConvoList);
4039     }
4040 }
4041