BluetoothMapContent.java revision 28446a56718e80574ba7b81901fc5a1368bf5839
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*/
15package com.android.bluetooth.map;
16
17import org.apache.http.util.ByteArrayBuffer;
18
19import android.content.ContentResolver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.net.Uri;
24import android.os.Debug;
25import android.os.ParcelFileDescriptor;
26import android.provider.BaseColumns;
27import com.android.bluetooth.mapapi.BluetoothMapContract;
28import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
29import android.provider.ContactsContract;
30import android.provider.ContactsContract.Contacts;
31import android.provider.ContactsContract.PhoneLookup;
32import android.provider.Telephony.Mms;
33import android.provider.Telephony.Sms;
34import android.telephony.PhoneNumberUtils;
35import android.telephony.TelephonyManager;
36import android.text.util.Rfc822Token;
37import android.text.util.Rfc822Tokenizer;
38import android.util.Log;
39
40import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
41import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
42import com.google.android.mms.pdu.CharacterSets;
43import com.google.android.mms.pdu.PduHeaders;
44import com.android.bluetooth.map.BluetoothMapAppParams;
45
46import java.io.ByteArrayOutputStream;
47import java.io.Closeable;
48import java.io.FileInputStream;
49import java.io.FileNotFoundException;
50import java.io.IOException;
51import java.io.InputStream;
52import java.io.UnsupportedEncodingException;
53import java.text.ParseException;
54import java.text.SimpleDateFormat;
55import java.util.ArrayList;
56import java.util.Arrays;
57import java.util.Date;
58import java.util.List;
59
60public 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
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
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
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
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
220    private static void close(Closeable c) {
221        try {
222          if (c != null) c.close();
223        } catch (IOException e) {
224        }
225    }
226
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     */
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
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
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
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     */
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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     */
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
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
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
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
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
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
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     * */
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
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
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
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
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
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
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
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    }
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
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
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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
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
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
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
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     */
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     */
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     */
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    */
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