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