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 org.apache.http.util.ByteArrayBuffer;
18 
19 import android.content.ContentResolver;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.net.Uri;
24 import android.os.Debug;
25 import android.os.ParcelFileDescriptor;
26 import android.provider.BaseColumns;
27 import com.android.bluetooth.mapapi.BluetoothMapContract;
28 import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
29 import android.provider.ContactsContract;
30 import android.provider.ContactsContract.Contacts;
31 import android.provider.ContactsContract.PhoneLookup;
32 import android.provider.Telephony.Mms;
33 import android.provider.Telephony.Sms;
34 import android.telephony.PhoneNumberUtils;
35 import android.telephony.TelephonyManager;
36 import android.text.util.Rfc822Token;
37 import android.text.util.Rfc822Tokenizer;
38 import android.util.Log;
39 
40 import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
41 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
42 import com.google.android.mms.pdu.CharacterSets;
43 import com.google.android.mms.pdu.PduHeaders;
44 import com.android.bluetooth.map.BluetoothMapAppParams;
45 
46 import java.io.ByteArrayOutputStream;
47 import java.io.Closeable;
48 import java.io.FileInputStream;
49 import java.io.FileNotFoundException;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.UnsupportedEncodingException;
53 import java.text.ParseException;
54 import java.text.SimpleDateFormat;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Date;
58 import java.util.List;
59 
60 public class BluetoothMapContent {
61     private static final String TAG = "BluetoothMapContent";
62 
63     private static final boolean D = BluetoothMapService.DEBUG;
64     private static final boolean V = BluetoothMapService.VERBOSE;
65 
66     private static final int MASK_SUBJECT = 0x1;
67     private static final int MASK_DATETIME = 0x2;
68     private static final int MASK_SENDER_NAME = 0x4;
69     private static final int MASK_SENDER_ADDRESSING = 0x8;
70 
71     private static final int MASK_RECIPIENT_NAME = 0x10;
72     private static final int MASK_RECIPIENT_ADDRESSING = 0x20;
73     private static final int MASK_TYPE = 0x40;
74     private static final int MASK_SIZE = 0x80;
75 
76     private static final int MASK_RECEPTION_STATUS = 0x100;
77     private static final int MASK_TEXT = 0x200;
78     private static final int MASK_ATTACHMENT_SIZE = 0x400;
79     private static final int MASK_PRIORITY = 0x800;
80 
81     private static final int MASK_READ = 0x1000;
82     private static final int MASK_SENT = 0x2000;
83     private static final int MASK_PROTECTED = 0x4000;
84     private static final int MASK_REPLYTO_ADDRESSING = 0x8000;
85 
86     /* Type of MMS address. From Telephony.java it must be one of PduHeaders.BCC, */
87     /* PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO. These are from PduHeaders.java */
88     public static final int MMS_FROM = 0x89;
89     public static final int MMS_TO = 0x97;
90     public static final int MMS_BCC = 0x81;
91     public static final int MMS_CC = 0x82;
92 
93     public static final String INSERT_ADDRES_TOKEN = "insert-address-token";
94 
95     private Context mContext;
96     private ContentResolver mResolver;
97     private String mBaseEmailUri = null;
98 
99     static final String[] SMS_PROJECTION = new String[] {
100         BaseColumns._ID,
101         Sms.THREAD_ID,
102         Sms.ADDRESS,
103         Sms.BODY,
104         Sms.DATE,
105         Sms.READ,
106         Sms.TYPE,
107         Sms.STATUS,
108         Sms.LOCKED,
109         Sms.ERROR_CODE
110     };
111 
112     static final String[] MMS_PROJECTION = new String[] {
113         BaseColumns._ID,
114         Mms.THREAD_ID,
115         Mms.MESSAGE_ID,
116         Mms.MESSAGE_SIZE,
117         Mms.SUBJECT,
118         Mms.CONTENT_TYPE,
119         Mms.TEXT_ONLY,
120         Mms.DATE,
121         Mms.DATE_SENT,
122         Mms.READ,
123         Mms.MESSAGE_BOX,
124         Mms.STATUS,
125         Mms.PRIORITY
126     };
127 
128     private class FilterInfo {
129         public static final int TYPE_SMS = 0;
130         public static final int TYPE_MMS = 1;
131         public static final int TYPE_EMAIL = 2;
132 
133         int mMsgType = TYPE_SMS;
134         int mPhoneType = 0;
135         String mPhoneNum = null;
136         String mPhoneAlphaTag = null;
137         /*column indices used to optimize queries */
138         public int mEmailColThreadId        = -1;
139         public int mEmailColProtected       = -1;
140         public int mEmailColFolder          = -1;
141         public int mMmsColFolder            = -1;
142         public int mSmsColFolder            = -1;
143         public int mEmailColRead            = -1;
144         public int mSmsColRead              = -1;
145         public int mMmsColRead              = -1;
146         public int mEmailColPriority        = -1;
147         public int mMmsColAttachmentSize    = -1;
148         public int mEmailColAttachment      = -1;
149         public int mEmailColAttachementSize = -1;
150         public int mMmsColTextOnly          = -1;
151         public int mMmsColId                = -1;
152         public int mSmsColId                = -1;
153         public int mEmailColSize            = -1;
154         public int mSmsColSubject           = -1;
155         public int mMmsColSize              = -1;
156         public int mEmailColToAddress       = -1;
157         public int mEmailColCcAddress       = -1;
158         public int mEmailColBccAddress      = -1;
159         public int mSmsColAddress           = -1;
160         public int mSmsColDate              = -1;
161         public int mMmsColDate              = -1;
162         public int mEmailColDate            = -1;
163         public int mMmsColSubject           = -1;
164         public int mEmailColSubject         = -1;
165         public int mSmsColType              = -1;
166         public int mEmailColFromAddress     = -1;
167         public int mEmailColId              = -1;
168 
169 
setEmailColumns(Cursor c)170         public void setEmailColumns(Cursor c) {
171             mEmailColThreadId        = c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID);
172             mEmailColProtected       = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_PROTECTED);
173             mEmailColFolder          = c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID);
174             mEmailColRead            = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ);
175             mEmailColPriority        = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY);
176             mEmailColAttachment      = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT);
177             mEmailColAttachementSize = c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE);
178             mEmailColSize            = c.getColumnIndex(BluetoothMapContract.MessageColumns.MESSAGE_SIZE);
179             mEmailColToAddress       = c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST);
180             mEmailColCcAddress       = c.getColumnIndex(BluetoothMapContract.MessageColumns.CC_LIST);
181             mEmailColBccAddress      = c.getColumnIndex(BluetoothMapContract.MessageColumns.BCC_LIST);
182             mEmailColDate            = c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE);
183             mEmailColSubject         = c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT);
184             mEmailColFromAddress     = c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST);
185             mEmailColId              = c.getColumnIndex(BluetoothMapContract.MessageColumns._ID);
186         }
187 
setSmsColumns(Cursor c)188         public void setSmsColumns(Cursor c) {
189             mSmsColId      = c.getColumnIndex(BaseColumns._ID);
190             mSmsColFolder  = c.getColumnIndex(Sms.TYPE);
191             mSmsColRead    = c.getColumnIndex(Sms.READ);
192             mSmsColSubject = c.getColumnIndex(Sms.BODY);
193             mSmsColAddress = c.getColumnIndex(Sms.ADDRESS);
194             mSmsColDate      = c.getColumnIndex(Sms.DATE);
195             mSmsColType      = c.getColumnIndex(Sms.TYPE);
196         }
197 
setMmsColumns(Cursor c)198         public void setMmsColumns(Cursor c) {
199             mMmsColId              = c.getColumnIndex(BaseColumns._ID);
200             mMmsColFolder          = c.getColumnIndex(Mms.MESSAGE_BOX);
201             mMmsColRead            = c.getColumnIndex(Mms.READ);
202             mMmsColAttachmentSize  = c.getColumnIndex(Mms.MESSAGE_SIZE);
203             mMmsColTextOnly        = c.getColumnIndex(Mms.TEXT_ONLY);
204             mMmsColSize            = c.getColumnIndex(Mms.MESSAGE_SIZE);
205             mMmsColDate            = c.getColumnIndex(Mms.DATE);
206             mMmsColSubject         = c.getColumnIndex(Mms.SUBJECT);
207 
208         }
209     }
210 
BluetoothMapContent(final Context context, String emailBaseUri)211     public BluetoothMapContent(final Context context, String emailBaseUri) {
212         mContext = context;
213         mResolver = mContext.getContentResolver();
214         if (mResolver == null) {
215             if (D) Log.d(TAG, "getContentResolver failed");
216         }
217         mBaseEmailUri = emailBaseUri;
218     }
219 
close(Closeable c)220     private static void close(Closeable c) {
221         try {
222           if (c != null) c.close();
223         } catch (IOException e) {
224         }
225     }
226 
setProtected(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)227     private void setProtected(BluetoothMapMessageListingElement e, Cursor c,
228             FilterInfo fi, BluetoothMapAppParams ap) {
229         if ((ap.getParameterMask() & MASK_PROTECTED) != 0) {
230             String protect = "no";
231             if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
232                 int flagProtected = c.getInt(fi.mEmailColProtected);
233                 if (flagProtected == 1) {
234                     protect = "yes";
235                 }
236             }
237             if (V) Log.d(TAG, "setProtected: " + protect + "\n");
238             e.setProtect(protect);
239         }
240     }
241 
242     /**
243      * Email only
244      */
setThreadId(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)245     private void setThreadId(BluetoothMapMessageListingElement e, Cursor c,
246             FilterInfo fi, BluetoothMapAppParams ap) {
247         if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
248             long threadId = c.getLong(fi.mEmailColThreadId);
249             e.setThreadId(threadId);
250             if (V) Log.d(TAG, "setThreadId: " + threadId + "\n");
251         }
252     }
253 
setSent(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)254     private void setSent(BluetoothMapMessageListingElement e, Cursor c,
255             FilterInfo fi, BluetoothMapAppParams ap) {
256         if ((ap.getParameterMask() & MASK_SENT) != 0) {
257             int msgType = 0;
258             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
259                 msgType = c.getInt(fi.mSmsColFolder);
260             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
261                 msgType = c.getInt(fi.mMmsColFolder);
262             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
263                 msgType = c.getInt(fi.mEmailColFolder);
264             }
265             String sent = null;
266             if (msgType == 2) {
267                 sent = "yes";
268             } else {
269                 sent = "no";
270             }
271             if (V) Log.d(TAG, "setSent: " + sent);
272             e.setSent(sent);
273         }
274     }
275 
setRead(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)276     private void setRead(BluetoothMapMessageListingElement e, Cursor c,
277             FilterInfo fi, BluetoothMapAppParams ap) {
278         int read = 0;
279         if (fi.mMsgType == FilterInfo.TYPE_SMS) {
280             read = c.getInt(fi.mSmsColRead);
281         } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
282             read = c.getInt(fi.mMmsColRead);
283         } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
284             read = c.getInt(fi.mEmailColRead);
285         }
286         String setread = null;
287 
288         if (V) Log.d(TAG, "setRead: " + setread);
289         e.setRead((read==1?true:false), ((ap.getParameterMask() & MASK_READ) != 0));
290     }
291 
setPriority(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)292     private void setPriority(BluetoothMapMessageListingElement e, Cursor c,
293             FilterInfo fi, BluetoothMapAppParams ap) {
294         if ((ap.getParameterMask() & MASK_PRIORITY) != 0) {
295             String priority = "no";
296             if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
297                 int highPriority = c.getInt(fi.mEmailColPriority);
298                 if (highPriority == 1) {
299                     priority = "yes";
300                 }
301             }
302             int pri = 0;
303             if (fi.mMsgType == FilterInfo.TYPE_MMS) {
304                 pri = c.getInt(c.getColumnIndex(Mms.PRIORITY));
305             }
306             if (pri == PduHeaders.PRIORITY_HIGH) {
307                 priority = "yes";
308             }
309             if (V) Log.d(TAG, "setPriority: " + priority);
310             e.setPriority(priority);
311         }
312     }
313 
314     /**
315      * For SMS we set the attachment size to 0, as all data will be text data, hence
316      * attachments for SMS is not possible.
317      * For MMS all data is actually attachments, hence we do set the attachment size to
318      * the total message size. To provide a more accurate attachment size, one could
319      * extract the length (in bytes) of the text parts.
320      */
setAttachmentSize(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)321     private void setAttachmentSize(BluetoothMapMessageListingElement e, Cursor c,
322             FilterInfo fi, BluetoothMapAppParams ap) {
323         if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) {
324             int size = 0;
325             if (fi.mMsgType == FilterInfo.TYPE_MMS) {
326                 if(c.getInt(fi.mMmsColTextOnly) == 0) {
327                     size = c.getInt(fi.mMmsColAttachmentSize);
328                     if(size <= 0) {
329                         // We know there are attachments, since it is not TextOnly
330                         // Hence the size in the database must be wrong.
331                         // Set size to 1 to indicate to the client, that attachments are present
332                         if (D) Log.d(TAG, "Error in message database, size reported as: " + size
333                                 + " Changing size to 1");
334                         size = 1;
335                     }
336                 }
337             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
338                 int attachment = c.getInt(fi.mEmailColAttachment);
339                 size = c.getInt(fi.mEmailColAttachementSize);
340                 if(attachment == 1 && size == 0) {
341                     if (D) Log.d(TAG, "Error in message database, attachment size reported as: " + size
342                             + " Changing size to 1");
343                     size = 1; /* Ensure we indicate we have attachments in the size, if the
344                                  message has attachments, in case the e-mail client do not
345                                  report a size */
346                 }
347             }
348             if (V) Log.d(TAG, "setAttachmentSize: " + size);
349             e.setAttachmentSize(size);
350         }
351     }
352 
setText(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)353     private void setText(BluetoothMapMessageListingElement e, Cursor c,
354             FilterInfo fi, BluetoothMapAppParams ap) {
355         if ((ap.getParameterMask() & MASK_TEXT) != 0) {
356             String hasText = "";
357             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
358                 hasText = "yes";
359             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
360                 int textOnly = c.getInt(fi.mMmsColTextOnly);
361                 if (textOnly == 1) {
362                     hasText = "yes";
363                 } else {
364                     long id = c.getLong(fi.mMmsColId);
365                     String text = getTextPartsMms(id);
366                     if (text != null && text.length() > 0) {
367                         hasText = "yes";
368                     } else {
369                         hasText = "no";
370                     }
371                 }
372             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
373                 hasText = "yes";
374             }
375             if (V) Log.d(TAG, "setText: " + hasText);
376             e.setText(hasText);
377         }
378     }
379 
setReceptionStatus(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)380     private void setReceptionStatus(BluetoothMapMessageListingElement e, Cursor c,
381         FilterInfo fi, BluetoothMapAppParams ap) {
382         if ((ap.getParameterMask() & MASK_RECEPTION_STATUS) != 0) {
383             String status = "complete";
384             if (V) Log.d(TAG, "setReceptionStatus: " + status);
385             e.setReceptionStatus(status);
386         }
387     }
388 
setSize(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)389     private void setSize(BluetoothMapMessageListingElement e, Cursor c,
390         FilterInfo fi, BluetoothMapAppParams ap) {
391         if ((ap.getParameterMask() & MASK_SIZE) != 0) {
392             int size = 0;
393             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
394                 String subject = c.getString(fi.mSmsColSubject);
395                 size = subject.length();
396             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
397                 size = c.getInt(fi.mMmsColSize);
398             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
399                 size = c.getInt(fi.mEmailColSize);
400             }
401             if(size <= 0) {
402                 // A message cannot have size 0
403                 // Hence the size in the database must be wrong.
404                 // Set size to 1 to indicate to the client, that the message has content.
405                 if (D) Log.d(TAG, "Error in message database, size reported as: " + size
406                         + " Changing size to 1");
407                 size = 1;
408             }
409             if (V) Log.d(TAG, "setSize: " + size);
410             e.setSize(size);
411         }
412     }
413 
setType(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)414     private void setType(BluetoothMapMessageListingElement e, Cursor c,
415         FilterInfo fi, BluetoothMapAppParams ap) {
416         if ((ap.getParameterMask() & MASK_TYPE) != 0) {
417             TYPE type = null;
418             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
419                 if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
420                     type = TYPE.SMS_GSM;
421                 } else if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_CDMA) {
422                     type = TYPE.SMS_CDMA;
423                 }
424             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
425                 type = TYPE.MMS;
426             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
427                 type = TYPE.EMAIL;
428             }
429             if (V) Log.d(TAG, "setType: " + type);
430             e.setType(type);
431         }
432     }
433 
setRecipientAddressingEmail(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi)434     private String setRecipientAddressingEmail(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi) {
435         String toAddress, ccAddress, bccAddress;
436         toAddress = c.getString(fi.mEmailColToAddress);
437         ccAddress = c.getString(fi.mEmailColCcAddress);
438         bccAddress = c.getString(fi.mEmailColBccAddress);
439 
440         String address = "";
441         if (toAddress != null) {
442             address += toAddress;
443             if (ccAddress != null) {
444                 address += ",";
445             }
446         }
447         if (ccAddress != null) {
448             address += ccAddress;
449             if (bccAddress != null) {
450                 address += ",";
451             }
452         }
453         if (bccAddress != null) {
454             address += bccAddress;
455         }
456         return address;
457     }
458 
setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)459     private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
460         FilterInfo fi, BluetoothMapAppParams ap) {
461         if ((ap.getParameterMask() & MASK_RECIPIENT_ADDRESSING) != 0) {
462             String address = null;
463             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
464                 int msgType = c.getInt(fi.mSmsColType);
465                 if (msgType == 1) {
466                     address = fi.mPhoneNum;
467                 } else {
468                     address = c.getString(c.getColumnIndex(Sms.ADDRESS));
469                 }
470             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
471                 long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
472                 address = getAddressMms(mResolver, id, MMS_TO);
473             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
474                 /* Might be another way to handle addresses */
475                 address = setRecipientAddressingEmail(e, c,fi);
476             }
477             if (V) Log.v(TAG, "setRecipientAddressing: " + address);
478             if(address == null)
479                 address = "";
480             e.setRecipientAddressing(address);
481         }
482     }
483 
setRecipientName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)484     private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c,
485         FilterInfo fi, BluetoothMapAppParams ap) {
486         if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) {
487             String name = null;
488             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
489                 int msgType = c.getInt(fi.mSmsColType);
490                 if (msgType != 1) {
491                     String phone = c.getString(fi.mSmsColAddress);
492                     if (phone != null && !phone.isEmpty())
493                         name = getContactNameFromPhone(phone);
494                 } else {
495                     name = fi.mPhoneAlphaTag;
496                 }
497             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
498                 long id = c.getLong(fi.mMmsColId);
499                 String phone;
500                 if(e.getRecipientAddressing() != null){
501                     phone = getAddressMms(mResolver, id, MMS_TO);
502                 } else {
503                     phone = e.getRecipientAddressing();
504                 }
505                 if (phone != null && !phone.isEmpty())
506                     name = getContactNameFromPhone(phone);
507             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
508                 /* Might be another way to handle address and names */
509                 name = setRecipientAddressingEmail(e,c,fi);
510             }
511             if (V) Log.v(TAG, "setRecipientName: " + name);
512             if(name == null)
513                 name = "";
514             e.setRecipientName(name);
515         }
516     }
517 
setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)518     private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c,
519             FilterInfo fi, BluetoothMapAppParams ap) {
520         if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
521             String address = null;
522             String tempAddress;
523             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
524                 int msgType = c.getInt(fi.mSmsColType);
525                 if (msgType == 1) { // INBOX
526                     tempAddress = c.getString(fi.mSmsColAddress);
527                 } else {
528                     tempAddress = fi.mPhoneNum;
529                 }
530                 if(tempAddress == null) {
531                     /* This can only happen on devices with no SIM -
532                        hence will typically not have any SMS messages. */
533                 } else {
534                     address = PhoneNumberUtils.extractNetworkPortion(tempAddress);
535                     /* extractNetworkPortion can return N if the number is a service "number" = a string
536                      * with the a name in (i.e. "Some-Tele-company" would return N because of the N in compaNy)
537                      * Hence we need to check if the number is actually a string with alpha chars.
538                      * */
539                     Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress).matches("[0-9]*[a-zA-Z]+[0-9]*");
540 
541                     if(address == null || address.length() < 2 || alpha) {
542                         address = tempAddress; // if the number is a service acsii text just use it
543                     }
544                 }
545             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
546                 long id = c.getLong(fi.mMmsColId);
547                 tempAddress = getAddressMms(mResolver, id, MMS_FROM);
548                 address = PhoneNumberUtils.extractNetworkPortion(tempAddress);
549                 if(address == null || address.length() < 1){
550                     address = tempAddress; // if the number is a service acsii text just use it
551                 }
552             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
553                 address = c.getString(fi.mEmailColFromAddress);
554             }
555             if (V) Log.v(TAG, "setSenderAddressing: " + address);
556             if(address == null)
557                 address = "";
558             e.setSenderAddressing(address);
559         }
560     }
561 
setSenderName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)562     private void setSenderName(BluetoothMapMessageListingElement e, Cursor c,
563             FilterInfo fi, BluetoothMapAppParams ap) {
564         if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
565             String name = null;
566             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
567                 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
568                 if (msgType == 1) {
569                     String phone = c.getString(fi.mSmsColAddress);
570                     if (phone != null && !phone.isEmpty())
571                         name = getContactNameFromPhone(phone);
572                 } else {
573                     name = fi.mPhoneAlphaTag;
574                 }
575             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
576                 long id = c.getLong(fi.mMmsColId);
577                 String phone;
578                 if(e.getSenderAddressing() != null){
579                     phone = getAddressMms(mResolver, id, MMS_FROM);
580                 } else {
581                     phone = e.getSenderAddressing();
582                 }
583                 if (phone != null && !phone.isEmpty() )
584                     name = getContactNameFromPhone(phone);
585             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
586                 name = c.getString(fi.mEmailColFromAddress);
587             }
588             if (V) Log.v(TAG, "setSenderName: " + name);
589             if(name == null)
590                 name = "";
591             e.setSenderName(name);
592         }
593     }
594 
setDateTime(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)595     private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
596             FilterInfo fi, BluetoothMapAppParams ap) {
597         if ((ap.getParameterMask() & MASK_DATETIME) != 0) {
598             long date = 0;
599             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
600                 date = c.getLong(fi.mSmsColDate);
601             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
602                 /* Use Mms.DATE for all messages. Although contract class states */
603                 /* Mms.DATE_SENT are for outgoing messages. But that is not working. */
604                 date = c.getLong(fi.mMmsColDate) * 1000L;
605 
606                 /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */
607                 /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */
608                 /*     date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */
609                 /* } else { */
610                 /*     date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
611                 /* } */
612             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
613                 date = c.getLong(fi.mEmailColDate);
614             }
615             e.setDateTime(date);
616         }
617     }
618 
getTextPartsMms(long id)619     private String getTextPartsMms(long id) {
620         String text = "";
621         String selection = new String("mid=" + id);
622         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part");
623         Uri uriAddress = Uri.parse(uriStr);
624         // TODO: maybe use a projection with only "ct" and "text"
625 
626         Cursor c = mResolver.query(uriAddress, null, selection, null, null);
627         try {
628             while(c != null && c.moveToNext()) {
629                 String ct = c.getString(c.getColumnIndex("ct"));
630                 if (ct.equals("text/plain")) {
631                     String part = c.getString(c.getColumnIndex("text"));
632                     if(part != null) {
633                         text += part;
634                     }
635                 }
636             }
637         } finally {
638             close(c);
639         }
640         return text;
641     }
642 
setSubject(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)643     private void setSubject(BluetoothMapMessageListingElement e, Cursor c,
644             FilterInfo fi, BluetoothMapAppParams ap) {
645         String subject = "";
646         int subLength = ap.getSubjectLength();
647         if(subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
648             subLength = 256;
649 
650         if ((ap.getParameterMask() & MASK_SUBJECT) != 0) {
651             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
652                 subject = c.getString(fi.mSmsColSubject);
653             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
654                 subject = c.getString(fi.mMmsColSubject);
655                 if (subject == null || subject.length() == 0) {
656                     /* Get subject from mms text body parts - if any exists */
657                     long id = c.getLong(fi.mMmsColId);
658                     subject = getTextPartsMms(id);
659                 }
660             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
661                 subject = c.getString(fi.mEmailColSubject);
662             }
663             if (subject != null && subject.length() > subLength) {
664                 subject = subject.substring(0, subLength);
665             }
666             if (V) Log.d(TAG, "setSubject: " + subject);
667             e.setSubject(subject);
668         }
669     }
670 
setHandle(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap)671     private void setHandle(BluetoothMapMessageListingElement e, Cursor c,
672             FilterInfo fi, BluetoothMapAppParams ap) {
673         long handle = -1;
674         if (fi.mMsgType == FilterInfo.TYPE_SMS) {
675             handle = c.getLong(fi.mSmsColId);
676         } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
677             handle = c.getLong(fi.mMmsColId);
678         } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
679             handle = c.getLong(fi.mEmailColId);
680         }
681         if (V) Log.d(TAG, "setHandle: " + handle );
682         e.setHandle(handle);
683     }
684 
element(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)685     private BluetoothMapMessageListingElement element(Cursor c, FilterInfo fi,
686             BluetoothMapAppParams ap) {
687         BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement();
688         setHandle(e, c, fi, ap);
689         setDateTime(e, c, fi, ap);
690         setType(e, c, fi, ap);
691         setRead(e, c, fi, ap);
692         // we set number and name for sender/recipient later
693         // they require lookup on contacts so no need to
694         // do it for all elements unless they are to be used.
695         e.setCursorIndex(c.getPosition());
696         return e;
697     }
698 
getContactNameFromPhone(String phone)699     private String getContactNameFromPhone(String phone) {
700         String name = null;
701 
702         Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
703             Uri.encode(phone));
704 
705         String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
706         String selection = Contacts.IN_VISIBLE_GROUP + "=1";
707         String orderBy = Contacts.DISPLAY_NAME + " ASC";
708 
709         Cursor c = mResolver.query(uri, projection, selection, null, orderBy);
710         try {
711             if (c != null && c.moveToFirst()) {
712                 name = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
713             };
714         } finally {
715             close(c);
716         }
717         return name;
718     }
719 
getAddressMms(ContentResolver r, long id, int type)720     static public String getAddressMms(ContentResolver r, long id, int type) {
721         String selection = new String("msg_id=" + id + " AND type=" + type);
722         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
723         Uri uriAddress = Uri.parse(uriStr);
724         String addr = null;
725 
726         Cursor c = r.query(uriAddress, null, selection, null, null);
727         try {
728             if (c != null && c.moveToFirst()) {
729                 addr = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
730                 if (addr.equals(INSERT_ADDRES_TOKEN)) addr  = "";
731             }
732         } finally {
733             close(c);
734         }
735 
736         return addr;
737     }
738 
739     /**
740      * Matching functions for originator and recipient for MMS
741      * @return true if found a match
742      */
matchRecipientMms(Cursor c, FilterInfo fi, String recip)743     private boolean matchRecipientMms(Cursor c, FilterInfo fi, String recip) {
744         boolean res;
745         long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
746         String phone = getAddressMms(mResolver, id, MMS_TO);
747         if (phone != null && phone.length() > 0) {
748             if (phone.matches(recip)) {
749                 if (V) Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone);
750                 res = true;
751             } else {
752                 String name = getContactNameFromPhone(phone);
753                 if (name != null && name.length() > 0 && name.matches(recip)) {
754                     if (V) Log.v(TAG, "matchRecipientMms: match recipient name = " + name);
755                     res = true;
756                 } else {
757                     res = false;
758                 }
759             }
760         } else {
761             res = false;
762         }
763         return res;
764     }
765 
matchRecipientSms(Cursor c, FilterInfo fi, String recip)766     private boolean matchRecipientSms(Cursor c, FilterInfo fi, String recip) {
767         boolean res;
768         int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
769         if (msgType == 1) {
770             String phone = fi.mPhoneNum;
771             String name = fi.mPhoneAlphaTag;
772             if (phone != null && phone.length() > 0 && phone.matches(recip)) {
773                 if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
774                 res = true;
775             } else if (name != null && name.length() > 0 && name.matches(recip)) {
776                 if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
777                 res = true;
778             } else {
779                 res = false;
780             }
781         } else {
782             String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
783             if (phone != null && phone.length() > 0) {
784                 if (phone.matches(recip)) {
785                     if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
786                     res = true;
787                 } else {
788                     String name = getContactNameFromPhone(phone);
789                     if (name != null && name.length() > 0 && name.matches(recip)) {
790                         if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
791                         res = true;
792                     } else {
793                         res = false;
794                     }
795                 }
796             } else {
797                 res = false;
798             }
799         }
800         return res;
801     }
802 
matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)803     private boolean matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
804         boolean res;
805         String recip = ap.getFilterRecipient();
806         if (recip != null && recip.length() > 0) {
807             recip = recip.replace("*", ".*");
808             recip = ".*" + recip + ".*";
809             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
810                 res = matchRecipientSms(c, fi, recip);
811             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
812                 res = matchRecipientMms(c, fi, recip);
813             } else {
814                 if (D) Log.d(TAG, "matchRecipient: Unknown msg type: " + fi.mMsgType);
815                 res = false;
816             }
817         } else {
818             res = true;
819         }
820         return res;
821     }
822 
matchOriginatorMms(Cursor c, FilterInfo fi, String orig)823     private boolean matchOriginatorMms(Cursor c, FilterInfo fi, String orig) {
824         boolean res;
825         long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
826         String phone = getAddressMms(mResolver, id, MMS_FROM);
827         if (phone != null && phone.length() > 0) {
828             if (phone.matches(orig)) {
829                 if (V) Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone);
830                 res = true;
831             } else {
832                 String name = getContactNameFromPhone(phone);
833                 if (name != null && name.length() > 0 && name.matches(orig)) {
834                     if (V) Log.v(TAG, "matchOriginatorMms: match originator name = " + name);
835                     res = true;
836                 } else {
837                     res = false;
838                 }
839             }
840         } else {
841             res = false;
842         }
843         return res;
844     }
845 
matchOriginatorSms(Cursor c, FilterInfo fi, String orig)846     private boolean matchOriginatorSms(Cursor c, FilterInfo fi, String orig) {
847         boolean res;
848         int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
849         if (msgType == 1) {
850             String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
851             if (phone !=null && phone.length() > 0) {
852                 if (phone.matches(orig)) {
853                     if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
854                     res = true;
855                 } else {
856                     String name = getContactNameFromPhone(phone);
857                     if (name != null && name.length() > 0 && name.matches(orig)) {
858                         if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
859                         res = true;
860                     } else {
861                         res = false;
862                     }
863                 }
864             } else {
865                 res = false;
866             }
867         } else {
868             String phone = fi.mPhoneNum;
869             String name = fi.mPhoneAlphaTag;
870             if (phone != null && phone.length() > 0 && phone.matches(orig)) {
871                 if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
872                 res = true;
873             } else if (name != null && name.length() > 0 && name.matches(orig)) {
874                 if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
875                 res = true;
876             } else {
877                 res = false;
878             }
879         }
880         return res;
881     }
882 
matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)883    private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
884         boolean res;
885         String orig = ap.getFilterOriginator();
886         if (orig != null && orig.length() > 0) {
887             orig = orig.replace("*", ".*");
888             orig = ".*" + orig + ".*";
889             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
890                 res = matchOriginatorSms(c, fi, orig);
891             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
892                 res = matchOriginatorMms(c, fi, orig);
893             } else {
894                 if(D) Log.d(TAG, "matchOriginator: Unknown msg type: " + fi.mMsgType);
895                 res = false;
896             }
897         } else {
898             res = true;
899         }
900         return res;
901     }
902 
matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap)903     private boolean matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
904         if (matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap)) {
905             return true;
906         } else {
907             return false;
908         }
909     }
910 
911     /*
912      * Where filter functions
913      * */
setWhereFilterFolderTypeSms(String folder)914     private String setWhereFilterFolderTypeSms(String folder) {
915         String where = "";
916         if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) {
917             where = Sms.TYPE + " = 1 AND " + Sms.THREAD_ID + " <> -1";
918         } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) {
919             where = "(" + Sms.TYPE + " = 4 OR " + Sms.TYPE + " = 5 OR "
920                     + Sms.TYPE + " = 6) AND " + Sms.THREAD_ID + " <> -1";
921         } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) {
922             where = Sms.TYPE + " = 2 AND " + Sms.THREAD_ID + " <> -1";
923         } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) {
924             where = Sms.TYPE + " = 3 AND " + Sms.THREAD_ID + " <> -1";
925         } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) {
926             where = Sms.THREAD_ID + " = -1";
927         }
928 
929         return where;
930     }
931 
setWhereFilterFolderTypeMms(String folder)932     private String setWhereFilterFolderTypeMms(String folder) {
933         String where = "";
934         if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) {
935             where = Mms.MESSAGE_BOX + " = 1 AND " + Mms.THREAD_ID + " <> -1";
936         } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) {
937             where = Mms.MESSAGE_BOX + " = 4 AND " + Mms.THREAD_ID + " <> -1";
938         } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) {
939             where = Mms.MESSAGE_BOX + " = 2 AND " + Mms.THREAD_ID + " <> -1";
940         } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) {
941             where = Mms.MESSAGE_BOX + " = 3 AND " + Mms.THREAD_ID + " <> -1";
942         } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) {
943             where = Mms.THREAD_ID + " = -1";
944         }
945 
946         return where;
947     }
948 
setWhereFilterFolderTypeEmail(long folderId)949     private String setWhereFilterFolderTypeEmail(long folderId) {
950         String where = "";
951         if (folderId >= 0) {
952             where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId;
953         } else {
954             Log.e(TAG, "setWhereFilterFolderTypeEmail: not valid!" );
955             throw new IllegalArgumentException("Invalid folder ID");
956         }
957         return where;
958     }
959 
setWhereFilterFolderType(BluetoothMapFolderElement folderElement, FilterInfo fi)960     private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement, FilterInfo fi) {
961         String where = "";
962         if (fi.mMsgType == FilterInfo.TYPE_SMS) {
963             where = setWhereFilterFolderTypeSms(folderElement.getName());
964         } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
965             where = setWhereFilterFolderTypeMms(folderElement.getName());
966         } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
967             where = setWhereFilterFolderTypeEmail(folderElement.getEmailFolderId());
968         }
969         return where;
970     }
971 
setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi)972     private String setWhereFilterReadStatus(BluetoothMapAppParams ap, FilterInfo fi) {
973         String where = "";
974         if (ap.getFilterReadStatus() != -1) {
975             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
976                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
977                     where = " AND " + Sms.READ + "= 0";
978                 }
979 
980                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
981                     where = " AND " + Sms.READ + "= 1";
982                 }
983             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
984                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
985                     where = " AND " + Mms.READ + "= 0";
986                 }
987 
988                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
989                     where = " AND " + Mms.READ + "= 1";
990                 }
991             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
992                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
993                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0";
994                 }
995 
996                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
997                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 1";
998                 }
999             }
1000         }
1001         return where;
1002     }
1003 
setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi)1004     private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) {
1005         String where = "";
1006         if ((ap.getFilterPeriodBegin() != -1)) {
1007             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1008             where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin();
1009             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1010                 where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L);
1011             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
1012                 where = " AND " + BluetoothMapContract.MessageColumns.DATE + " >= " + (ap.getFilterPeriodBegin());
1013             }
1014         }
1015 
1016         if ((ap.getFilterPeriodEnd() != -1)) {
1017             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
1018             where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd();
1019             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
1020                 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
1021             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
1022                 where += " AND " + BluetoothMapContract.MessageColumns.DATE + " < " + (ap.getFilterPeriodEnd());
1023             }
1024         }
1025 
1026 
1027         return where;
1028     }
1029 
setWhereFilterPhones(String str)1030     private String setWhereFilterPhones(String str) {
1031         String where = "";
1032         str = str.replace("*", "%");
1033 
1034         Cursor c = mResolver.query(ContactsContract.Contacts.CONTENT_URI, null,
1035             ContactsContract.Contacts.DISPLAY_NAME + " like ?",
1036             new String[]{str},
1037             ContactsContract.Contacts.DISPLAY_NAME + " ASC");
1038 
1039         try {
1040             while (c != null && c.moveToNext()) {
1041                 String contactId = c.getString(c.getColumnIndex(ContactsContract.Contacts._ID));
1042 
1043                 Cursor p = mResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
1044                     ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
1045                     new String[]{contactId},
1046                     null);
1047 
1048                 try {
1049                     while (p != null && p.moveToNext()) {
1050                         String number = p.getString(
1051                             p.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
1052 
1053                         where += " address = " + "'" + number + "'";
1054                         if (!p.isLast()) where += " OR ";
1055                     }
1056                 } finally {
1057                     close(p);
1058                 }
1059 
1060                 if (!c.isLast()) where += " OR ";
1061             }
1062         } finally {
1063             close(c);
1064         }
1065 
1066         if (str != null && str.length() > 0) {
1067             if (where.length() > 0) {
1068                 where += " OR ";
1069             }
1070             where += " address like " + "'" + str + "'";
1071         }
1072 
1073         return where;
1074     }
1075 
setWhereFilterOriginatorEmail(BluetoothMapAppParams ap)1076     private String setWhereFilterOriginatorEmail(BluetoothMapAppParams ap) {
1077         String where = "";
1078         String orig = ap.getFilterOriginator();
1079 
1080         /* Be aware of wild cards in the beginning of string, may not be valid? */
1081         if (orig != null && orig.length() > 0) {
1082             orig = orig.replace("*", "%");
1083             where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST + " LIKE '%" +  orig + "%'";
1084         }
1085         return where;
1086     }
setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi)1087     private String setWhereFilterPriority(BluetoothMapAppParams ap, FilterInfo fi) {
1088         String where = "";
1089         int pri = ap.getFilterPriority();
1090         /*only MMS have priority info */
1091         if(fi.mMsgType == FilterInfo.TYPE_MMS)
1092         {
1093             if(pri == 0x0002)
1094             {
1095                 where += " AND " + Mms.PRIORITY + "<=" +
1096                     Integer.toString(PduHeaders.PRIORITY_NORMAL);
1097             }else if(pri == 0x0001) {
1098                 where += " AND " + Mms.PRIORITY + "=" +
1099                     Integer.toString(PduHeaders.PRIORITY_HIGH);
1100             }
1101         }
1102         return where;
1103     }
1104 
setWhereFilterRecipientEmail(BluetoothMapAppParams ap)1105     private String setWhereFilterRecipientEmail(BluetoothMapAppParams ap) {
1106         String where = "";
1107         String recip = ap.getFilterRecipient();
1108 
1109         /* Be aware of wild cards in the beginning of string, may not be valid? */
1110         if (recip != null && recip.length() > 0) {
1111             recip = recip.replace("*", "%");
1112             where = " AND ("
1113             + BluetoothMapContract.MessageColumns.TO_LIST  + " LIKE '%" + recip + "%' OR "
1114             + BluetoothMapContract.MessageColumns.CC_LIST  + " LIKE '%" + recip + "%' OR "
1115             + BluetoothMapContract.MessageColumns.BCC_LIST + " LIKE '%" + recip + "%' )";
1116         }
1117         return where;
1118     }
1119 
setWhereFilter(BluetoothMapFolderElement folderElement, FilterInfo fi, BluetoothMapAppParams ap)1120     private String setWhereFilter(BluetoothMapFolderElement folderElement,
1121             FilterInfo fi, BluetoothMapAppParams ap) {
1122         String where = "";
1123 
1124         where += setWhereFilterFolderType(folderElement, fi);
1125         if(!where.isEmpty()) {
1126             where += setWhereFilterReadStatus(ap, fi);
1127             where += setWhereFilterPeriod(ap, fi);
1128             where += setWhereFilterPriority(ap,fi);
1129 
1130             if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
1131                 where += setWhereFilterOriginatorEmail(ap);
1132                 where += setWhereFilterRecipientEmail(ap);
1133             }
1134         }
1135 
1136 
1137         return where;
1138     }
1139 
1140     /**
1141      * Determine from application parameter if sms should be included.
1142      * The filter mask is set for message types not selected
1143      * @param fi
1144      * @param ap
1145      * @return boolean true if sms is selected, false if not
1146      */
smsSelected(FilterInfo fi, BluetoothMapAppParams ap)1147     private boolean smsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
1148         int msgType = ap.getFilterMessageType();
1149         int phoneType = fi.mPhoneType;
1150 
1151         if (D) Log.d(TAG, "smsSelected msgType: " + msgType);
1152 
1153         if (msgType == -1)
1154             return true;
1155 
1156         if ((msgType & 0x03) == 0)
1157             return true;
1158 
1159         if (((msgType & 0x01) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
1160             return true;
1161 
1162         if (((msgType & 0x02) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
1163             return true;
1164 
1165         return false;
1166     }
1167 
1168     /**
1169      * Determine from application parameter if mms should be included.
1170      * The filter mask is set for message types not selected
1171      * @param fi
1172      * @param ap
1173      * @return boolean true if sms is selected, false if not
1174      */
mmsSelected(FilterInfo fi, BluetoothMapAppParams ap)1175     private boolean mmsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
1176         int msgType = ap.getFilterMessageType();
1177 
1178         if (D) Log.d(TAG, "mmsSelected msgType: " + msgType);
1179 
1180         if (msgType == -1)
1181             return true;
1182 
1183         if ((msgType & 0x08) == 0)
1184             return true;
1185 
1186         return false;
1187     }
1188 
1189     /**
1190      * Determine from application parameter if email should be included.
1191      * The filter mask is set for message types not selected
1192      * @param fi
1193      * @param ap
1194      * @return boolean true if sms is selected, false if not
1195      */
emailSelected(FilterInfo fi, BluetoothMapAppParams ap)1196     private boolean emailSelected(FilterInfo fi, BluetoothMapAppParams ap) {
1197         int msgType = ap.getFilterMessageType();
1198 
1199         if (D) Log.d(TAG, "emailSelected msgType: " + msgType);
1200 
1201         if (msgType == -1)
1202             return true;
1203 
1204         if ((msgType & 0x04) == 0)
1205             return true;
1206 
1207         return false;
1208     }
1209 
setFilterInfo(FilterInfo fi)1210     private void setFilterInfo(FilterInfo fi) {
1211         TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
1212         if (tm != null) {
1213             fi.mPhoneType = tm.getPhoneType();
1214             fi.mPhoneNum = tm.getLine1Number();
1215             fi.mPhoneAlphaTag = tm.getLine1AlphaTag();
1216             if (D) Log.d(TAG, "phone type = " + fi.mPhoneType +
1217                 " phone num = " + fi.mPhoneNum +
1218                 " phone alpha tag = " + fi.mPhoneAlphaTag);
1219         }
1220     }
1221 
1222     /**
1223      * Get a listing of message in folder after applying filter.
1224      * @param folder Must contain a valid folder string != null
1225      * @param ap Parameters specifying message content and filters
1226      * @return Listing object containing requested messages
1227      */
msgListing(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)1228     public BluetoothMapMessageListing msgListing(BluetoothMapFolderElement folderElement,
1229             BluetoothMapAppParams ap) {
1230         if (D) Log.d(TAG, "msgListing: folderName = " + folderElement.getName()
1231                 + " folderId = " + folderElement.getEmailFolderId()
1232                 + " messageType = " + ap.getFilterMessageType() );
1233         BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
1234 
1235 
1236         /* We overwrite the parameter mask here if it is 0 or not present, as this
1237          * should cause all parameters to be included in the message list. */
1238         if(ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
1239                 ap.getParameterMask() == 0) {
1240             ap.setParameterMask(BluetoothMapAppParams.PARAMETER_MASK_ALL_ENABLED);
1241             if (V) Log.v(TAG, "msgListing(): appParameterMask is zero or not present, " +
1242                     "changing to: " + ap.getParameterMask());
1243         }
1244 
1245         /* Cache some info used throughout filtering */
1246         FilterInfo fi = new FilterInfo();
1247         setFilterInfo(fi);
1248         Cursor smsCursor = null;
1249         Cursor mmsCursor = null;
1250         Cursor emailCursor = null;
1251 
1252         try {
1253             String limit = "";
1254             int countNum = ap.getMaxListCount();
1255             int offsetNum = ap.getStartOffset();
1256             if(ap.getMaxListCount()>0){
1257                 limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset());
1258             }
1259 
1260             if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
1261                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
1262                                                  BluetoothMapAppParams.FILTER_NO_MMS|
1263                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM)||
1264                    ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
1265                                                  BluetoothMapAppParams.FILTER_NO_MMS|
1266                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA)){
1267                     //set real limit and offset if only this type is used (only if offset/limit is used
1268                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
1269                     if(D) Log.d(TAG, "SMS Limit => "+limit);
1270                     offsetNum = 0;
1271                 }
1272                 fi.mMsgType = FilterInfo.TYPE_SMS;
1273                 if(ap.getFilterPriority() != 1){ /*SMS cannot have high priority*/
1274                     String where = setWhereFilter(folderElement, fi, ap);
1275                     if(!where.isEmpty()) {
1276                         if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
1277                         smsCursor = mResolver.query(Sms.CONTENT_URI,
1278                                 SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit);
1279                         if (smsCursor != null) {
1280                             BluetoothMapMessageListingElement e = null;
1281                             // store column index so we dont have to look them up anymore (optimization)
1282                             if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
1283                             fi.setSmsColumns(smsCursor);
1284                             while (smsCursor.moveToNext()) {
1285                                 if (matchAddresses(smsCursor, fi, ap)) {
1286                                     e = element(smsCursor, fi, ap);
1287                                     bmList.add(e);
1288                                 }
1289                             }
1290                         }
1291                     }
1292                 }
1293             }
1294 
1295             if (mmsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
1296                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
1297                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
1298                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM)){
1299                     //set real limit and offset if only this type is used (only if offset/limit is used
1300                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
1301                     if(D) Log.d(TAG, "MMS Limit => "+limit);
1302                     offsetNum = 0;
1303                 }
1304                 fi.mMsgType = FilterInfo.TYPE_MMS;
1305                 String where = setWhereFilter(folderElement, fi, ap);
1306                 if(!where.isEmpty()) {
1307                     if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
1308                     mmsCursor = mResolver.query(Mms.CONTENT_URI,
1309                             MMS_PROJECTION, where, null, Mms.DATE + " DESC" + limit);
1310                     if (mmsCursor != null) {
1311                         BluetoothMapMessageListingElement e = null;
1312                         // store column index so we dont have to look them up anymore (optimization)
1313                         fi.setMmsColumns(mmsCursor);
1314                         int cnt = 0;
1315                         if(D) Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages.");
1316                         while (mmsCursor.moveToNext()) {
1317                             if (matchAddresses(mmsCursor, fi, ap)) {
1318                                 e = element(mmsCursor, fi, ap);
1319                                 bmList.add(e);
1320                             }
1321                         }
1322                     }
1323                 }
1324             }
1325 
1326             if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
1327                 if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
1328                                                  BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
1329                                                  BluetoothMapAppParams.FILTER_NO_SMS_GSM)){
1330                     //set real limit and offset if only this type is used (only if offset/limit is used
1331                     limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
1332                     if(D) Log.d(TAG, "Email Limit => "+limit);
1333                     offsetNum = 0;
1334                 }
1335                 fi.mMsgType = FilterInfo.TYPE_EMAIL;
1336                 String where = setWhereFilter(folderElement, fi, ap);
1337 
1338                 if(!where.isEmpty()) {
1339                     if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
1340                     Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
1341                     emailCursor = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
1342                             where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
1343                     if (emailCursor != null) {
1344                         BluetoothMapMessageListingElement e = null;
1345                         // store column index so we dont have to look them up anymore (optimization)
1346                         fi.setEmailColumns(emailCursor);
1347                         int cnt = 0;
1348                         while (emailCursor.moveToNext()) {
1349                             if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
1350                             e = element(emailCursor, fi, ap);
1351                             bmList.add(e);
1352                         }
1353                     //   emailCursor.close();
1354                     }
1355                 }
1356             }
1357 
1358             /* Enable this if post sorting and segmenting needed */
1359             bmList.sort();
1360             bmList.segment(ap.getMaxListCount(), offsetNum);
1361             List<BluetoothMapMessageListingElement> list = bmList.getList();
1362             int listSize = list.size();
1363             Cursor tmpCursor = null;
1364             for (int x=0; x<listSize; x++){
1365                 BluetoothMapMessageListingElement ele = list.get(x);
1366                 if ((ele.getType().equals(TYPE.SMS_GSM)||ele.getType().equals(TYPE.SMS_CDMA)) && smsCursor != null){
1367                     tmpCursor = smsCursor;
1368                     fi.mMsgType = FilterInfo.TYPE_SMS;
1369                 } else if (ele.getType().equals(TYPE.MMS) && mmsCursor != null){
1370                     tmpCursor = mmsCursor;
1371                     fi.mMsgType = FilterInfo.TYPE_MMS;
1372                 } else if (ele.getType().equals(TYPE.EMAIL) && emailCursor != null){
1373                     tmpCursor = emailCursor;
1374                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
1375                 }
1376 
1377                 if (tmpCursor != null && tmpCursor.moveToPosition(ele.getCursorIndex())) {
1378                     setSenderAddressing(ele, tmpCursor, fi, ap);
1379                     setSenderName(ele, tmpCursor, fi, ap);
1380                     setRecipientAddressing(ele, tmpCursor, fi, ap);
1381                     setRecipientName(ele, tmpCursor, fi, ap);
1382                     setSubject(ele, tmpCursor, fi, ap);
1383                     setSize(ele, tmpCursor, fi, ap);
1384                     setReceptionStatus(ele, tmpCursor, fi, ap);
1385                     setText(ele, tmpCursor, fi, ap);
1386                     setAttachmentSize(ele, tmpCursor, fi, ap);
1387                     setPriority(ele, tmpCursor, fi, ap);
1388                     setSent(ele, tmpCursor, fi, ap);
1389                     setProtected(ele, tmpCursor, fi, ap);
1390                     setThreadId(ele, tmpCursor, fi, ap);
1391                 }
1392             }
1393         } finally {
1394             close(emailCursor);
1395             close(smsCursor);
1396             close(mmsCursor);
1397         }
1398 
1399         if (D) Log.d(TAG, "messagelisting end");
1400         return bmList;
1401     }
1402 
1403     /**
1404      * Get the size of the message listing
1405      * @param folder Must contain a valid folder string != null
1406      * @param ap Parameters specifying message content and filters
1407      * @return Integer equal to message listing size
1408      */
msgListingSize(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)1409     public int msgListingSize(BluetoothMapFolderElement folderElement,
1410             BluetoothMapAppParams ap) {
1411         if (D) Log.d(TAG, "msgListingSize: folder = " + folderElement.getName());
1412         int cnt = 0;
1413 
1414         /* Cache some info used throughout filtering */
1415         FilterInfo fi = new FilterInfo();
1416         setFilterInfo(fi);
1417 
1418         if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
1419             fi.mMsgType = FilterInfo.TYPE_SMS;
1420             String where = setWhereFilter(folderElement, fi, ap);
1421             Cursor c = mResolver.query(Sms.CONTENT_URI,
1422                     SMS_PROJECTION, where, null, Sms.DATE + " DESC");
1423 
1424             if (c != null) cnt = c.getCount();
1425             close(c);
1426         }
1427 
1428         if (mmsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
1429             fi.mMsgType = FilterInfo.TYPE_MMS;
1430             String where = setWhereFilter(folderElement, fi, ap);
1431             Cursor c = mResolver.query(Mms.CONTENT_URI,
1432                     MMS_PROJECTION, where, null, Mms.DATE + " DESC");
1433             if (c != null) cnt += c.getCount();
1434             close(c);
1435         }
1436 
1437         if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
1438             fi.mMsgType = FilterInfo.TYPE_EMAIL;
1439             String where = setWhereFilter(folderElement, fi, ap);
1440             if (!where.isEmpty()) {
1441                 Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
1442                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
1443                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
1444                 if (c != null) cnt += c.getCount();
1445                 close(c);
1446             }
1447         }
1448 
1449         if (D) Log.d(TAG, "msgListingSize: size = " + cnt);
1450         return cnt;
1451     }
1452 
1453     /**
1454      * Return true if there are unread messages in the requested list of messages
1455      * @param folder folder where the message listing should come from
1456      * @param ap application parameter object
1457      * @return true if unread messages are in the list, else false
1458      */
msgListingHasUnread(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap)1459     public boolean msgListingHasUnread(BluetoothMapFolderElement folderElement,
1460             BluetoothMapAppParams ap) {
1461         if (D) Log.d(TAG, "msgListingHasUnread: folder = " + folderElement.getName());
1462         int cnt = 0;
1463 
1464         /* Cache some info used throughout filtering */
1465         FilterInfo fi = new FilterInfo();
1466         setFilterInfo(fi);
1467 
1468        if (smsSelected(fi, ap)  && folderElement.hasSmsMmsContent()) {
1469             fi.mMsgType = FilterInfo.TYPE_SMS;
1470             String where = setWhereFilterFolderType(folderElement, fi);
1471             where += " AND " + Sms.READ + "=0 ";
1472             where += setWhereFilterPeriod(ap, fi);
1473             Cursor c = mResolver.query(Sms.CONTENT_URI,
1474                 SMS_PROJECTION, where, null, Sms.DATE + " DESC");
1475 
1476             if (c != null) cnt += c.getCount();
1477             close(c);
1478         }
1479 
1480         if (mmsSelected(fi, ap)  && folderElement.hasSmsMmsContent()) {
1481             fi.mMsgType = FilterInfo.TYPE_MMS;
1482             String where = setWhereFilterFolderType(folderElement, fi);
1483             where += " AND " + Mms.READ + "=0 ";
1484             where += setWhereFilterPeriod(ap, fi);
1485             Cursor c = mResolver.query(Mms.CONTENT_URI,
1486                 MMS_PROJECTION, where, null, Sms.DATE + " DESC");
1487 
1488             if (c != null) cnt += c.getCount();
1489             close(c);
1490         }
1491 
1492 
1493         if (emailSelected(fi, ap) && folderElement.getEmailFolderId() != -1) {
1494             fi.mMsgType = FilterInfo.TYPE_EMAIL;
1495             String where = setWhereFilterFolderType(folderElement, fi);
1496             if(!where.isEmpty()) {
1497                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
1498                 where += setWhereFilterPeriod(ap, fi);
1499                 Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
1500                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
1501                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
1502                 if (c != null) cnt += c.getCount();
1503                 close(c);
1504             }
1505         }
1506 
1507         if (D) Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt);
1508         return (cnt>0)?true:false;
1509     }
1510 
1511     /**
1512      * Get the folder name of an SMS message or MMS message.
1513      * @param c the cursor pointing at the message
1514      * @return the folder name.
1515      */
getFolderName(int type, int threadId)1516     private String getFolderName(int type, int threadId) {
1517 
1518         if(threadId == -1)
1519             return BluetoothMapContract.FOLDER_NAME_DELETED;
1520 
1521         switch(type) {
1522         case 1:
1523             return BluetoothMapContract.FOLDER_NAME_INBOX;
1524         case 2:
1525             return BluetoothMapContract.FOLDER_NAME_SENT;
1526         case 3:
1527             return BluetoothMapContract.FOLDER_NAME_DRAFT;
1528         case 4: // Just name outbox, failed and queued "outbox"
1529         case 5:
1530         case 6:
1531             return BluetoothMapContract.FOLDER_NAME_OUTBOX;
1532         }
1533         return "";
1534     }
1535 
getMessage(String handle, BluetoothMapAppParams appParams, BluetoothMapFolderElement folderElement)1536     public byte[] getMessage(String handle, BluetoothMapAppParams appParams,
1537             BluetoothMapFolderElement folderElement) throws UnsupportedEncodingException{
1538         TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
1539         long id = BluetoothMapUtils.getCpHandle(handle);
1540         if(appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) {
1541             throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as" +
1542                                                " we always return the full message.");
1543         }
1544         switch(type) {
1545         case SMS_GSM:
1546         case SMS_CDMA:
1547             return getSmsMessage(id, appParams.getCharset());
1548         case MMS:
1549             return getMmsMessage(id, appParams);
1550         case EMAIL:
1551             return getEmailMessage(id, appParams, folderElement);
1552         }
1553         throw new IllegalArgumentException("Invalid message handle.");
1554     }
1555 
setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming)1556     private String setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming) {
1557         String contactId = null, contactName = null;
1558         String[] phoneNumbers = null;
1559         String[] emailAddresses = null;
1560 
1561         Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
1562                 Uri.encode(phone));
1563 
1564         String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
1565         String selection = Contacts.IN_VISIBLE_GROUP + "=1";
1566         String orderBy = Contacts._ID + " ASC";
1567 
1568         // Get the contact _ID and name
1569         Cursor p = mResolver.query(uri, projection, selection, null, orderBy);
1570 
1571         try {
1572             if (p != null && p.moveToFirst()) {
1573                 contactId = p.getString(p.getColumnIndex(Contacts._ID));
1574                 contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
1575             }
1576 
1577             // Bail out if we are unable to find a contact, based on the phone number
1578             if(contactId == null) {
1579                 phoneNumbers = new String[1];
1580                 phoneNumbers[0] = phone;
1581             } else {
1582                 // use only actual phone number
1583                 phoneNumbers = new String[1];
1584                 phoneNumbers[0] = phone;
1585 
1586                 // Fetch contact e-mail addresses
1587                 close (p);
1588                 p = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
1589                         ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
1590                         new String[]{contactId},
1591                         null);
1592                 if (p != null) {
1593                     int i = 0;
1594                     emailAddresses = new String[p.getCount()];
1595                     while (p != null && p.moveToNext()) {
1596                         String emailAddress = p.getString(
1597                             p.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS));
1598                         emailAddresses[i++] = emailAddress;
1599                     }
1600                 }
1601             }
1602         } finally {
1603             close(p);
1604         }
1605 
1606         if (incoming == true) {
1607             if(V) Log.d(TAG, "Adding originator for phone:" + phone);
1608             message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
1609         } else {
1610             if(V) Log.d(TAG, "Adding recipient for phone:" + phone);
1611             message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
1612         }
1613         return contactName;
1614     }
1615 
1616     public static final int MAP_MESSAGE_CHARSET_NATIVE = 0;
1617     public static final int MAP_MESSAGE_CHARSET_UTF8 = 1;
1618 
getSmsMessage(long id, int charset)1619     public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException{
1620         int type, threadId;
1621         long time = -1;
1622         String msgBody;
1623         BluetoothMapbMessageSms message = new BluetoothMapbMessageSms();
1624         TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
1625 
1626         Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null);
1627         if (c == null || !c.moveToFirst()) {
1628             throw new IllegalArgumentException("SMS handle not found");
1629         }
1630 
1631         try {
1632             if(V) Log.v(TAG,"c.count: " + c.getCount());
1633 
1634             if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
1635                 message.setType(TYPE.SMS_GSM);
1636             } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1637                 message.setType(TYPE.SMS_CDMA);
1638             }
1639 
1640             String read = c.getString(c.getColumnIndex(Sms.READ));
1641             if (read.equalsIgnoreCase("1"))
1642                 message.setStatus(true);
1643             else
1644                 message.setStatus(false);
1645 
1646             type = c.getInt(c.getColumnIndex(Sms.TYPE));
1647             threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
1648             message.setFolder(getFolderName(type, threadId));
1649 
1650             msgBody = c.getString(c.getColumnIndex(Sms.BODY));
1651 
1652             String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
1653 
1654             time = c.getLong(c.getColumnIndex(Sms.DATE));
1655             if(type == 1) // Inbox message needs to set the vCard as originator
1656                 setVCardFromPhoneNumber(message, phone, true);
1657             else          // Other messages sets the vCard as the recipient
1658                 setVCardFromPhoneNumber(message, phone, false);
1659 
1660             if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
1661                 if(type == 1) //Inbox
1662                     message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time));
1663                 else
1664                     message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
1665             } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
1666                 message.setSmsBody(msgBody);
1667             }
1668         } finally {
1669             close(c);
1670         }
1671 
1672         return message.encode();
1673     }
1674 
extractMmsAddresses(long id, BluetoothMapbMessageMms message)1675     private void extractMmsAddresses(long id, BluetoothMapbMessageMms message) {
1676         final String[] projection = null;
1677         String selection = new String(Mms.Addr.MSG_ID + "=" + id);
1678         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
1679         Uri uriAddress = Uri.parse(uriStr);
1680         String contactName = null;
1681 
1682         Cursor c = mResolver.query( uriAddress, projection, selection, null, null);
1683         try {
1684             while (c != null && c.moveToNext()) {
1685                 String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
1686                 if(address.equals(INSERT_ADDRES_TOKEN))
1687                     continue;
1688                 Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
1689                 switch(type) {
1690                 case MMS_FROM:
1691                     contactName = setVCardFromPhoneNumber(message, address, true);
1692                     message.addFrom(contactName, address);
1693                     break;
1694                 case MMS_TO:
1695                     contactName = setVCardFromPhoneNumber(message, address, false);
1696                     message.addTo(contactName, address);
1697                     break;
1698                 case MMS_CC:
1699                     contactName = setVCardFromPhoneNumber(message, address, false);
1700                     message.addCc(contactName, address);
1701                     break;
1702                 case MMS_BCC:
1703                     contactName = setVCardFromPhoneNumber(message, address, false);
1704                     message.addBcc(contactName, address);
1705                     break;
1706                 default:
1707                     break;
1708                 }
1709             }
1710         } finally {
1711             close (c);
1712         }
1713     }
1714 
1715     /**
1716      * Read out a mms data part and return the data in a byte array.
1717      * @param partid the content provider id of the mms.
1718      * @return
1719      */
readMmsDataPart(long partid)1720     private byte[] readMmsDataPart(long partid) {
1721         String uriStr = new String(Mms.CONTENT_URI + "/part/" + partid);
1722         Uri uriAddress = Uri.parse(uriStr);
1723         InputStream is = null;
1724         ByteArrayOutputStream os = new ByteArrayOutputStream();
1725         int bufferSize = 8192;
1726         byte[] buffer = new byte[bufferSize];
1727         byte[] retVal = null;
1728 
1729         try {
1730             is = mResolver.openInputStream(uriAddress);
1731             int len = 0;
1732             while ((len = is.read(buffer)) != -1) {
1733               os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
1734             }
1735             retVal = os.toByteArray();
1736         } catch (IOException e) {
1737             // do nothing for now
1738             Log.w(TAG,"Error reading part data",e);
1739         } finally {
1740             close(os);
1741             close(is);
1742         }
1743         return retVal;
1744     }
1745 
1746     /**
1747      * Read out the mms parts and update the bMessage object provided i {@linkplain message}
1748      * @param id the content provider ID of the message
1749      * @param message the bMessage object to add the information to
1750      */
extractMmsParts(long id, BluetoothMapbMessageMms message)1751     private void extractMmsParts(long id, BluetoothMapbMessageMms message)
1752     {
1753         /* Handling of filtering out non-text parts for exclude
1754          * attachments is handled within the bMessage object. */
1755         final String[] projection = null;
1756         String selection = new String(Mms.Part.MSG_ID + "=" + id);
1757         String uriStr = new String(Mms.CONTENT_URI + "/"+ id + "/part");
1758         Uri uriAddress = Uri.parse(uriStr);
1759         BluetoothMapbMessageMms.MimePart part;
1760         Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
1761 
1762         try {
1763             while(c != null && c.moveToNext()) {
1764                 Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
1765                 String contentType = c.getString(c.getColumnIndex(Mms.Part.CONTENT_TYPE));
1766                 String name = c.getString(c.getColumnIndex(Mms.Part.NAME));
1767                 String charset = c.getString(c.getColumnIndex(Mms.Part.CHARSET));
1768                 String filename = c.getString(c.getColumnIndex(Mms.Part.FILENAME));
1769                 String text = c.getString(c.getColumnIndex(Mms.Part.TEXT));
1770                 Integer fd = c.getInt(c.getColumnIndex(Mms.Part._DATA));
1771                 String cid = c.getString(c.getColumnIndex(Mms.Part.CONTENT_ID));
1772                 String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION));
1773                 String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION));
1774 
1775                 if(V) Log.d(TAG, "     _id : " + partId +
1776                         "\n     ct : " + contentType +
1777                         "\n     partname : " + name +
1778                         "\n     charset : " + charset +
1779                         "\n     filename : " + filename +
1780                         "\n     text : " + text +
1781                         "\n     fd : " + fd +
1782                         "\n     cid : " + cid +
1783                         "\n     cl : " + cl +
1784                         "\n     cdisp : " + cdisp);
1785 
1786                 part = message.addMimePart();
1787                 part.mContentType = contentType;
1788                 part.mPartName = name;
1789                 part.mContentId = cid;
1790                 part.mContentLocation = cl;
1791                 part.mContentDisposition = cdisp;
1792 
1793                 try {
1794                     if(text != null) {
1795                         part.mData = text.getBytes("UTF-8");
1796                         part.mCharsetName = "utf-8";
1797                     } else {
1798                         part.mData = readMmsDataPart(partId);
1799                         if(charset != null)
1800                             part.mCharsetName = CharacterSets.getMimeName(Integer.parseInt(charset));
1801                     }
1802                 } catch (NumberFormatException e) {
1803                     Log.d(TAG,"extractMmsParts",e);
1804                     part.mData = null;
1805                     part.mCharsetName = null;
1806                 } catch (UnsupportedEncodingException e) {
1807                     Log.d(TAG,"extractMmsParts",e);
1808                     part.mData = null;
1809                     part.mCharsetName = null;
1810                 }
1811                 part.mFileName = filename;
1812             }
1813         } finally {
1814             close(c);
1815         }
1816 
1817         message.updateCharset();
1818     }
1819 
1820     /**
1821      *
1822      * @param id the content provider id for the message to fetch.
1823      * @param appParams The application parameter object received from the client.
1824      * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
1825      * @throws UnsupportedEncodingException if UTF-8 is not supported,
1826      * which is guaranteed to be supported on an android device
1827      */
getMmsMessage(long id, BluetoothMapAppParams appParams)1828     public byte[] getMmsMessage(long id, BluetoothMapAppParams appParams) throws UnsupportedEncodingException {
1829         int msgBox, threadId;
1830         if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
1831             throw new IllegalArgumentException("MMS charset native not allowed for MMS - must be utf-8");
1832 
1833         BluetoothMapbMessageMms message = new BluetoothMapbMessageMms();
1834         Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null);
1835         if (c == null || !c.moveToFirst()) {
1836             throw new IllegalArgumentException("MMS handle not found");
1837         }
1838 
1839         try {
1840             message.setType(TYPE.MMS);
1841 
1842             // The MMS info:
1843             String read = c.getString(c.getColumnIndex(Mms.READ));
1844             if (read.equalsIgnoreCase("1"))
1845                 message.setStatus(true);
1846             else
1847                 message.setStatus(false);
1848 
1849             msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
1850             threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
1851             message.setFolder(getFolderName(msgBox, threadId));
1852             message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT)));
1853             message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
1854             message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
1855             message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
1856             message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true);
1857             message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
1858 
1859             extractMmsParts(id, message);
1860             extractMmsAddresses(id, message);
1861         } finally {
1862             close(c);
1863         }
1864 
1865         return message.encode();
1866     }
1867 
1868     /**
1869     *
1870     * @param id the content provider id for the message to fetch.
1871     * @param appParams The application parameter object received from the client.
1872     * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
1873     * @throws UnsupportedEncodingException if UTF-8 is not supported,
1874     * which is guaranteed to be supported on an android device
1875     */
getEmailMessage(long id, BluetoothMapAppParams appParams, BluetoothMapFolderElement currentFolder)1876    public byte[] getEmailMessage(long id, BluetoothMapAppParams appParams,
1877            BluetoothMapFolderElement currentFolder) throws UnsupportedEncodingException {
1878        // Log print out of application parameters set
1879        if(D && appParams != null) {
1880            Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() +
1881                    ", Charset = " + appParams.getCharset() +
1882                    ", FractionRequest = " + appParams.getFractionRequest());
1883        }
1884 
1885        // Throw exception if requester NATIVE charset for Email
1886        // Exception is caught by MapObexServer sendGetMessageResp
1887        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
1888            throw new IllegalArgumentException("EMAIL charset not UTF-8");
1889 
1890        BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail();
1891        Uri contentUri = Uri.parse(mBaseEmailUri + BluetoothMapContract.TABLE_MESSAGE);
1892        Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
1893        if (c != null && c.moveToFirst()) {
1894            throw new IllegalArgumentException("EMAIL handle not found");
1895        }
1896 
1897        try {
1898            BluetoothMapFolderElement folderElement;
1899 
1900            // Handle fraction requests
1901            int fractionRequest = appParams.getFractionRequest();
1902            if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
1903                // Fraction requested
1904                if(V) {
1905                    String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
1906                    Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
1907                            +  " - send compete message" );
1908                }
1909                // Check if message is complete and if not - request message from server
1910                if (c.getString(c.getColumnIndex(
1911                        BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase(
1912                                BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false)  {
1913                    // TODO: request message from server
1914                    Log.w(TAG, "getEmailMessage - receptionState not COMPLETE -  Not Implemented!" );
1915                }
1916            }
1917            // Set read status:
1918            String read = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
1919            if (read != null && read.equalsIgnoreCase("1"))
1920                message.setStatus(true);
1921            else
1922                message.setStatus(false);
1923 
1924            // Set message type:
1925            message.setType(TYPE.EMAIL);
1926 
1927            // Set folder:
1928            long folderId = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
1929            folderElement = currentFolder.getEmailFolderById(folderId);
1930            message.setCompleteFolder(folderElement.getFullPath());
1931 
1932            // Set recipient:
1933            String nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
1934            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
1935            if (tokens.length != 0) {
1936                if(D) Log.d(TAG, "Recipient count= " + tokens.length);
1937                int i = 0;
1938                while (i < tokens.length) {
1939                    if(V) Log.d(TAG, "Recipient = " + tokens[i].toString());
1940                    String[] emails = new String[1];
1941                    emails[0] = tokens[i].getAddress();
1942                    String name = tokens[i].getName();
1943                    message.addRecipient(name, name, null, emails);
1944                    i++;
1945                }
1946            }
1947 
1948            // Set originator:
1949            nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
1950            tokens = Rfc822Tokenizer.tokenize(nameEmail);
1951            if (tokens.length != 0) {
1952                if(D) Log.d(TAG, "Originator count= " + tokens.length);
1953                int i = 0;
1954                while (i < tokens.length) {
1955                    if(V) Log.d(TAG, "Originator = " + tokens[i].toString());
1956                    String[] emails = new String[1];
1957                    emails[0] = tokens[i].getAddress();
1958                    String name = tokens[i].getName();
1959                    message.addOriginator(name, name, null, emails);
1960                    i++;
1961                }
1962            }
1963 
1964            // Find out if we get attachments
1965            String attStr = (appParams.getAttachment() == 0) ?  "/" +  BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
1966            Uri uri = Uri.parse(contentUri + "/" + id + attStr);
1967 
1968            // Get email message body content
1969            int count = 0;
1970            FileInputStream is = null;
1971            ParcelFileDescriptor fd = null;
1972 
1973            try {
1974                fd = mResolver.openFileDescriptor(uri, "r");
1975                is = new FileInputStream(fd.getFileDescriptor());
1976                StringBuilder email = new StringBuilder("");
1977                byte[] buffer = new byte[1024];
1978                while((count = is.read(buffer)) != -1) {
1979                    // TODO: Handle breaks within a UTF8 character
1980                    email.append(new String(buffer,0,count));
1981                    if(V) Log.d(TAG, "Email part = " + new String(buffer,0,count) + " count=" + count);
1982                }
1983                // Set email message body:
1984                message.setEmailBody(email.toString());
1985            } catch (FileNotFoundException e) {
1986                Log.w(TAG, e);
1987            } catch (NullPointerException e) {
1988                Log.w(TAG, e);
1989            } catch (IOException e) {
1990                Log.w(TAG, e);
1991            } finally {
1992                close(is);
1993                close(fd);
1994            }
1995        } finally {
1996            close(c);
1997        }
1998 
1999        return message.encode();
2000    }
2001 }
2002