1/*
2* Copyright (C) 2013 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 java.io.ByteArrayOutputStream;
18import java.io.IOException;
19import java.io.InputStream;
20import java.io.UnsupportedEncodingException;
21import java.text.ParseException;
22
23import org.apache.http.util.ByteArrayBuffer;
24
25import android.content.ContentResolver;
26import android.content.ContentValues;
27import android.content.Context;
28import android.database.Cursor;
29import android.net.Uri;
30import android.provider.BaseColumns;
31import android.provider.ContactsContract;
32import android.provider.ContactsContract.Contacts;
33import android.provider.ContactsContract.PhoneLookup;
34import android.provider.Telephony.Mms;
35import android.provider.Telephony.Sms;
36import android.telephony.TelephonyManager;
37import android.util.Log;
38
39import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
40import com.google.android.mms.pdu.CharacterSets;
41
42public class BluetoothMapContent {
43    private static final String TAG = "BluetoothMapContent";
44
45    private static final boolean D = false;
46    private static final boolean V = false;
47
48    private static final int MASK_SUBJECT = 0x1;
49    private static final int MASK_DATETIME = 0x2;
50    private static final int MASK_SENDER_NAME = 0x4;
51    private static final int MASK_SENDER_ADDRESSING = 0x8;
52
53    private static final int MASK_RECIPIENT_NAME = 0x10;
54    private static final int MASK_RECIPIENT_ADDRESSING = 0x20;
55    private static final int MASK_TYPE = 0x40;
56    private static final int MASK_SIZE = 0x80;
57
58    private static final int MASK_RECEPTION_STATUS = 0x100;
59    private static final int MASK_TEXT = 0x200;
60    private static final int MASK_ATTACHMENT_SIZE = 0x400;
61    private static final int MASK_PRIORITY = 0x800;
62
63    private static final int MASK_READ = 0x1000;
64    private static final int MASK_SENT = 0x2000;
65    private static final int MASK_PROTECTED = 0x4000;
66    private static final int MASK_REPLYTO_ADDRESSING = 0x8000;
67
68    /* Type of MMS address. From Telephony.java it must be one of PduHeaders.BCC, */
69    /* PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO. These are from PduHeaders.java */
70    public static final int MMS_FROM = 0x89;
71    public static final int MMS_TO = 0x97;
72    public static final int MMS_BCC = 0x81;
73    public static final int MMS_CC = 0x82;
74
75    private Context mContext;
76    private ContentResolver mResolver;
77
78    static final String[] SMS_PROJECTION = new String[] {
79        BaseColumns._ID,
80        Sms.THREAD_ID,
81        Sms.ADDRESS,
82        Sms.BODY,
83        Sms.DATE,
84        Sms.READ,
85        Sms.TYPE,
86        Sms.STATUS,
87        Sms.LOCKED,
88        Sms.ERROR_CODE,
89    };
90
91    static final String[] MMS_PROJECTION = new String[] {
92        BaseColumns._ID,
93        Mms.THREAD_ID,
94        Mms.MESSAGE_ID,
95        Mms.MESSAGE_SIZE,
96        Mms.SUBJECT,
97        Mms.CONTENT_TYPE,
98        Mms.TEXT_ONLY,
99        Mms.DATE,
100        Mms.DATE_SENT,
101        Mms.READ,
102        Mms.MESSAGE_BOX,
103        Mms.STATUS,
104    };
105
106    private class FilterInfo {
107        public static final int TYPE_SMS = 0;
108        public static final int TYPE_MMS = 1;
109
110        int msgType = TYPE_SMS;
111        int phoneType = 0;
112        String phoneNum = null;
113        String phoneAlphaTag = null;
114    }
115
116    public BluetoothMapContent(final Context context) {
117        mContext = context;
118        mResolver = mContext.getContentResolver();
119        if (mResolver == null) {
120            Log.d(TAG, "getContentResolver failed");
121        }
122    }
123
124    private void addSmsEntry() {
125        if (D) Log.d(TAG, "*** Adding dummy sms ***");
126
127        ContentValues mVal = new ContentValues();
128        mVal.put(Sms.ADDRESS, "1234");
129        mVal.put(Sms.BODY, "Hello!!!");
130        mVal.put(Sms.DATE, System.currentTimeMillis());
131        mVal.put(Sms.READ, "0");
132
133        Uri mUri = mResolver.insert(Sms.CONTENT_URI, mVal);
134    }
135
136    private BluetoothMapAppParams buildAppParams() {
137        BluetoothMapAppParams ap = new BluetoothMapAppParams();
138        try {
139            int paramMask = (MASK_SUBJECT
140                | MASK_DATETIME
141                | MASK_SENDER_NAME
142                | MASK_SENDER_ADDRESSING
143                | MASK_RECIPIENT_NAME
144                | MASK_RECIPIENT_ADDRESSING
145                | MASK_TYPE
146                | MASK_SIZE
147                | MASK_RECEPTION_STATUS
148                | MASK_TEXT
149                | MASK_ATTACHMENT_SIZE
150                | MASK_PRIORITY
151                | MASK_READ
152                | MASK_SENT
153                | MASK_PROTECTED
154                );
155            ap.setMaxListCount(5);
156            ap.setStartOffset(0);
157            ap.setFilterMessageType(0);
158            ap.setFilterPeriodBegin("20130101T000000");
159            ap.setFilterPeriodEnd("20131230T000000");
160            ap.setFilterReadStatus(0);
161            ap.setParameterMask(paramMask);
162            ap.setSubjectLength(10);
163            /* ap.setFilterOriginator("Sms*"); */
164            /* ap.setFilterRecipient("41*"); */
165        } catch (ParseException e) {
166            return null;
167        }
168        return ap;
169    }
170
171    private void printSms(Cursor c) {
172        String body = c.getString(c.getColumnIndex(Sms.BODY));
173        if (D) Log.d(TAG, "printSms " + BaseColumns._ID + ": " + c.getLong(c.getColumnIndex(BaseColumns._ID)) +
174                " " + Sms.THREAD_ID + " : " + c.getLong(c.getColumnIndex(Sms.THREAD_ID)) +
175                " " + Sms.ADDRESS + " : " + c.getString(c.getColumnIndex(Sms.ADDRESS)) +
176                " " + Sms.BODY + " : " + body.substring(0, Math.min(body.length(), 8)) +
177                " " + Sms.DATE + " : " + c.getLong(c.getColumnIndex(Sms.DATE)) +
178                " " + Sms.TYPE + " : " + c.getInt(c.getColumnIndex(Sms.TYPE)));
179    }
180
181    private void printMms(Cursor c) {
182        if (D) Log.d(TAG, "printMms " + BaseColumns._ID + ": " + c.getLong(c.getColumnIndex(BaseColumns._ID)) +
183                "\n   " + Mms.THREAD_ID + " : " + c.getLong(c.getColumnIndex(Mms.THREAD_ID)) +
184                "\n   " + Mms.MESSAGE_ID + " : " + c.getString(c.getColumnIndex(Mms.MESSAGE_ID)) +
185                "\n   " + Mms.SUBJECT + " : " + c.getString(c.getColumnIndex(Mms.SUBJECT)) +
186                "\n   " + Mms.CONTENT_TYPE + " : " + c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)) +
187                "\n   " + Mms.TEXT_ONLY + " : " + c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) +
188                "\n   " + Mms.DATE + " : " + c.getLong(c.getColumnIndex(Mms.DATE)) +
189                "\n   " + Mms.DATE_SENT + " : " + c.getLong(c.getColumnIndex(Mms.DATE_SENT)) +
190                "\n   " + Mms.READ + " : " + c.getInt(c.getColumnIndex(Mms.READ)) +
191                "\n   " + Mms.MESSAGE_BOX + " : " + c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)) +
192                "\n   " + Mms.STATUS + " : " + c.getInt(c.getColumnIndex(Mms.STATUS)));
193    }
194
195    private void printMmsAddr(long id) {
196        final String[] projection = null;
197        String selection = new String("msg_id=" + id);
198        String uriStr = String.format("content://mms/%d/addr", id);
199        Uri uriAddress = Uri.parse(uriStr);
200        Cursor c = mResolver.query(
201            uriAddress,
202            projection,
203            selection,
204            null, null);
205
206        if (c.moveToFirst()) {
207            do {
208                String add = c.getString(c.getColumnIndex("address"));
209                Integer type = c.getInt(c.getColumnIndex("type"));
210                if (type == MMS_TO) {
211                    if (D) Log.d(TAG, "   recipient: " + add + " (type: " + type + ")");
212                } else if (type == MMS_FROM) {
213                    if (D) Log.d(TAG, "   originator: " + add + " (type: " + type + ")");
214                } else {
215                    if (D) Log.d(TAG, "   address other: " + add + " (type: " + type + ")");
216                }
217
218            } while(c.moveToNext());
219        }
220    }
221
222    private void printMmsPartImage(long partid) {
223        String uriStr = String.format("content://mms/part/%d", partid);
224        Uri uriAddress = Uri.parse(uriStr);
225        int ch;
226        StringBuffer sb = new StringBuffer("");
227        InputStream is = null;
228
229        try {
230            is = mResolver.openInputStream(uriAddress);
231
232            while ((ch = is.read()) != -1) {
233                sb.append((char)ch);
234            }
235            if (D) Log.d(TAG, sb.toString());
236
237        } catch (IOException e) {
238            // do nothing for now
239            e.printStackTrace();
240        }
241    }
242
243    private void printMmsParts(long id) {
244        final String[] projection = null;
245        String selection = new String("mid=" + id);
246        String uriStr = String.format("content://mms/%d/part", id);
247        Uri uriAddress = Uri.parse(uriStr);
248        Cursor c = mResolver.query(
249            uriAddress,
250            projection,
251            selection,
252            null, null);
253
254        if (D) Log.d(TAG, "   parts:");
255        if (c.moveToFirst()) {
256            do {
257                Long partid = c.getLong(c.getColumnIndex(BaseColumns._ID));
258                String ct = c.getString(c.getColumnIndex("ct"));
259                String name = c.getString(c.getColumnIndex("name"));
260                String charset = c.getString(c.getColumnIndex("chset"));
261                String filename = c.getString(c.getColumnIndex("fn"));
262                String text = c.getString(c.getColumnIndex("text"));
263                Integer fd = c.getInt(c.getColumnIndex("_data"));
264                String cid = c.getString(c.getColumnIndex("cid"));
265                String cl = c.getString(c.getColumnIndex("cl"));
266                String cdisp = c.getString(c.getColumnIndex("cd"));
267
268                if (D) Log.d(TAG, "     _id : " + partid +
269                    "\n     ct : " + ct +
270                    "\n     partname : " + name +
271                    "\n     charset : " + charset +
272                    "\n     filename : " + filename +
273                    "\n     text : " + text +
274                    "\n     fd : " + fd +
275                    "\n     cid : " + cid +
276                    "\n     cl : " + cl +
277                    "\n     cdisp : " + cdisp);
278
279                /* if (ct.equals("image/jpeg")) { */
280                /*     printMmsPartImage(partid); */
281                /* } */
282            } while(c.moveToNext());
283        }
284    }
285
286    public void dumpMmsTable() {
287        if (D) Log.d(TAG, "**** Dump of mms table ****");
288        Cursor c = mResolver.query(Mms.CONTENT_URI,
289                MMS_PROJECTION, null, null, "_id DESC");
290        if (c != null) {
291            if (D) Log.d(TAG, "c.getCount() = " + c.getCount());
292            c.moveToPosition(-1);
293            while (c.moveToNext()) {
294                printMms(c);
295                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
296                printMmsAddr(id);
297                printMmsParts(id);
298            }
299        } else {
300            Log.d(TAG, "query failed");
301            c.close();
302        }
303    }
304
305    public void dumpSmsTable() {
306        addSmsEntry();
307        if (D) Log.d(TAG, "**** Dump of sms table ****");
308        Cursor c = mResolver.query(Sms.CONTENT_URI,
309                SMS_PROJECTION, null, null, "_id DESC");
310        if (c != null) {
311            if (D) Log.d(TAG, "c.getCount() = " + c.getCount());
312            c.moveToPosition(-1);
313            while (c.moveToNext()) {
314                printSms(c);
315            }
316        } else {
317            Log.d(TAG, "query failed");
318            c.close();
319        }
320
321    }
322
323    public void dumpMessages() {
324        dumpSmsTable();
325        dumpMmsTable();
326
327        BluetoothMapAppParams ap = buildAppParams();
328        if (D) Log.d(TAG, "message listing size = " + msgListingSize("inbox", ap));
329        BluetoothMapMessageListing mList = msgListing("inbox", ap);
330        try {
331            mList.encode();
332        } catch (UnsupportedEncodingException ex) {
333            /* do nothing */
334        }
335        mList = msgListing("sent", ap);
336        try {
337            mList.encode();
338        } catch (UnsupportedEncodingException ex) {
339            /* do nothing */
340        }
341    }
342
343    private void setProtected(BluetoothMapMessageListingElement e, Cursor c,
344        FilterInfo fi, BluetoothMapAppParams ap) {
345        if ((ap.getParameterMask() & MASK_PROTECTED) != 0) {
346            String protect = "no";
347            if (D) Log.d(TAG, "setProtected: " + protect);
348            e.setProtect(protect);
349        }
350    }
351
352    private void setSent(BluetoothMapMessageListingElement e, Cursor c,
353        FilterInfo fi, BluetoothMapAppParams ap) {
354        if ((ap.getParameterMask() & MASK_SENT) != 0) {
355            int msgType = 0;
356            if (fi.msgType == FilterInfo.TYPE_SMS) {
357                msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
358            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
359                msgType = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
360            }
361            String sent = null;
362            if (msgType == 2) {
363                sent = "yes";
364            } else {
365                sent = "no";
366            }
367            if (D) Log.d(TAG, "setSent: " + sent);
368            e.setSent(sent);
369        }
370    }
371
372    private void setRead(BluetoothMapMessageListingElement e, Cursor c,
373        FilterInfo fi, BluetoothMapAppParams ap) {
374        int read = 0;
375        if (fi.msgType == FilterInfo.TYPE_SMS) {
376            read = c.getInt(c.getColumnIndex(Sms.READ));
377        } else if (fi.msgType == FilterInfo.TYPE_MMS) {
378            read = c.getInt(c.getColumnIndex(Mms.READ));
379        }
380        String setread = null;
381        if (read == 1) {
382            setread = "yes";
383        } else {
384            setread = "no";
385        }
386        if (D) Log.d(TAG, "setRead: " + setread);
387        e.setRead(setread, ((ap.getParameterMask() & MASK_READ) != 0));
388    }
389
390    private void setPriority(BluetoothMapMessageListingElement e, Cursor c,
391        FilterInfo fi, BluetoothMapAppParams ap) {
392        if ((ap.getParameterMask() & MASK_PRIORITY) != 0) {
393            String priority = "no";
394            if (D) Log.d(TAG, "setPriority: " + priority);
395            e.setPriority(priority);
396        }
397    }
398
399    /**
400     * For SMS we set the attachment size to 0, as all data will be text data, hence
401     * attachments for SMS is not possible.
402     * For MMS all data is actually attachments, hence we do set the attachment size to
403     * the total message size. To provide a more accurate attachment size, one could
404     * extract the length (in bytes) of the text parts.
405     */
406    private void setAttachmentSize(BluetoothMapMessageListingElement e, Cursor c,
407        FilterInfo fi, BluetoothMapAppParams ap) {
408        if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) {
409            int size = 0;
410            if (fi.msgType == FilterInfo.TYPE_MMS) {
411                size = c.getInt(c.getColumnIndex(Mms.MESSAGE_SIZE));
412            }
413            if (D) Log.d(TAG, "setAttachmentSize: " + size);
414            e.setAttachmentSize(size);
415        }
416    }
417
418    private void setText(BluetoothMapMessageListingElement e, Cursor c,
419        FilterInfo fi, BluetoothMapAppParams ap) {
420        if ((ap.getParameterMask() & MASK_TEXT) != 0) {
421            String hasText = "";
422            if (fi.msgType == FilterInfo.TYPE_SMS) {
423                hasText = "yes";
424            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
425                int textOnly = c.getInt(c.getColumnIndex(Mms.TEXT_ONLY));
426                if (textOnly == 1) {
427                    hasText = "yes";
428                } else {
429                    long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
430                    String text = getTextPartsMms(id);
431                    if (text != null && text.length() > 0) {
432                        hasText = "yes";
433                    } else {
434                        hasText = "no";
435                    }
436                }
437            }
438            if (D) Log.d(TAG, "setText: " + hasText);
439            e.setText(hasText);
440        }
441    }
442
443    private void setReceptionStatus(BluetoothMapMessageListingElement e, Cursor c,
444        FilterInfo fi, BluetoothMapAppParams ap) {
445        if ((ap.getParameterMask() & MASK_RECEPTION_STATUS) != 0) {
446            String status = "complete";
447            if (D) Log.d(TAG, "setReceptionStatus: " + status);
448            e.setReceptionStatus(status);
449        }
450    }
451
452    private void setSize(BluetoothMapMessageListingElement e, Cursor c,
453        FilterInfo fi, BluetoothMapAppParams ap) {
454        if ((ap.getParameterMask() & MASK_SIZE) != 0) {
455            int size = 0;
456            if (fi.msgType == FilterInfo.TYPE_SMS) {
457                String subject = c.getString(c.getColumnIndex(Sms.BODY));
458                size = subject.length();
459            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
460                size = c.getInt(c.getColumnIndex(Mms.MESSAGE_SIZE));
461            }
462            if (D) Log.d(TAG, "setSize: " + size);
463            e.setSize(size);
464        }
465    }
466
467    private void setType(BluetoothMapMessageListingElement e, Cursor c,
468        FilterInfo fi, BluetoothMapAppParams ap) {
469        if ((ap.getParameterMask() & MASK_TYPE) != 0) {
470            TYPE type = null;
471            if (fi.msgType == FilterInfo.TYPE_SMS) {
472                if (fi.phoneType == TelephonyManager.PHONE_TYPE_GSM) {
473                    type = TYPE.SMS_GSM;
474                } else if (fi.phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
475                    type = TYPE.SMS_CDMA;
476                }
477            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
478                type = TYPE.MMS;
479            }
480            if (D) Log.d(TAG, "setType: " + type);
481            e.setType(type);
482        }
483    }
484
485    private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
486        FilterInfo fi, BluetoothMapAppParams ap) {
487        if ((ap.getParameterMask() & MASK_RECIPIENT_ADDRESSING) != 0) {
488            String address = null;
489            if (fi.msgType == FilterInfo.TYPE_SMS) {
490                int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
491                if (msgType == 1) {
492                    address = fi.phoneNum;
493                } else {
494                    address = c.getString(c.getColumnIndex(Sms.ADDRESS));
495                }
496            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
497                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
498                address = getAddressMms(mResolver, id, MMS_TO);
499            }
500            if (D) Log.d(TAG, "setRecipientAddressing: " + address);
501            e.setRecipientAddressing(address);
502        }
503    }
504
505    private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c,
506        FilterInfo fi, BluetoothMapAppParams ap) {
507        if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) {
508            String name = null;
509            if (fi.msgType == FilterInfo.TYPE_SMS) {
510                int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
511                if (msgType != 1) {
512                    String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
513                    name = getContactNameFromPhone(phone);
514                } else {
515                    name = fi.phoneAlphaTag;
516                }
517            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
518                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
519                String phone = getAddressMms(mResolver, id, MMS_TO);
520                name = getContactNameFromPhone(phone);
521            }
522            if (D) Log.d(TAG, "setRecipientName: " + name);
523            e.setRecipientName(name);
524        }
525    }
526
527    private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c,
528        FilterInfo fi, BluetoothMapAppParams ap) {
529        if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
530            String address = null;
531            if (fi.msgType == FilterInfo.TYPE_SMS) {
532                int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
533                if (msgType == 1) {
534                    address = c.getString(c.getColumnIndex(Sms.ADDRESS));
535                } else {
536                    address = fi.phoneNum;
537                }
538            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
539                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
540                address = getAddressMms(mResolver, id, MMS_FROM);
541            }
542            if (D) Log.d(TAG, "setSenderAddressing: " + address);
543            e.setSenderAddressing(address);
544        }
545    }
546
547    private void setSenderName(BluetoothMapMessageListingElement e, Cursor c,
548        FilterInfo fi, BluetoothMapAppParams ap) {
549        if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
550            String name = null;
551            if (fi.msgType == FilterInfo.TYPE_SMS) {
552                int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
553                if (msgType == 1) {
554                    String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
555                    name = getContactNameFromPhone(phone);
556                } else {
557                    name = fi.phoneAlphaTag;
558                }
559            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
560                long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
561                String phone = getAddressMms(mResolver, id, MMS_FROM);
562                name = getContactNameFromPhone(phone);
563            }
564            if (D) Log.d(TAG, "setSenderName: " + name);
565            e.setSenderName(name);
566        }
567    }
568
569    private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
570        FilterInfo fi, BluetoothMapAppParams ap) {
571        long date = 0;
572
573        if (fi.msgType == FilterInfo.TYPE_SMS) {
574            date = c.getLong(c.getColumnIndex(Sms.DATE));
575        } else if (fi.msgType == FilterInfo.TYPE_MMS) {
576            /* Use Mms.DATE for all messages. Although contract class states */
577            /* Mms.DATE_SENT are for outgoing messages. But that is not working. */
578            date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L;
579
580            /* int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX)); */
581            /* if (msgBox == Mms.MESSAGE_BOX_INBOX) { */
582            /*     date = c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L; */
583            /* } else { */
584            /*     date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
585            /* } */
586        }
587        e.setDateTime(date);
588    }
589
590    private String getTextPartsMms(long id) {
591        String text = "";
592        String selection = new String("mid=" + id);
593        String uriStr = String.format("content://mms/%d/part", id);
594        Uri uriAddress = Uri.parse(uriStr);
595        Cursor c = mResolver.query(uriAddress, null, selection,
596            null, null);
597
598        if (c != null && c.moveToFirst()) {
599            do {
600                String ct = c.getString(c.getColumnIndex("ct"));
601                if (ct.equals("text/plain")) {
602                    text += c.getString(c.getColumnIndex("text"));
603                }
604            } while(c.moveToNext());
605        }
606        if (c != null) {
607            c.close();
608        }
609        return text;
610    }
611
612    private void setSubject(BluetoothMapMessageListingElement e, Cursor c,
613        FilterInfo fi, BluetoothMapAppParams ap) {
614        String subject = "";
615        int subLength = ap.getSubjectLength();
616        if(subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
617            subLength = 256;
618
619        if ((ap.getParameterMask() & MASK_SUBJECT) != 0) {
620            if (fi.msgType == FilterInfo.TYPE_SMS) {
621                subject = c.getString(c.getColumnIndex(Sms.BODY));
622            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
623                subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
624                if (subject == null || subject.length() == 0) {
625                    /* Get subject from mms text body parts - if any exists */
626                    long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
627                    subject = getTextPartsMms(id);
628                }
629            }
630            if (subject != null) {
631                subject = subject.substring(0, Math.min(subject.length(),
632                    subLength));
633            }
634            if (D) Log.d(TAG, "setSubject: " + subject);
635            e.setSubject(subject);
636        }
637    }
638
639    private void setHandle(BluetoothMapMessageListingElement e, Cursor c,
640        FilterInfo fi, BluetoothMapAppParams ap) {
641        long handle = c.getLong(c.getColumnIndex(BaseColumns._ID));
642        TYPE type = null;
643        if (fi.msgType == FilterInfo.TYPE_SMS) {
644            if (fi.phoneType == TelephonyManager.PHONE_TYPE_GSM) {
645                type = TYPE.SMS_GSM;
646            } else if (fi.phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
647                type = TYPE.SMS_CDMA;
648            }
649        } else if (fi.msgType == FilterInfo.TYPE_MMS) {
650            type = TYPE.MMS;
651        }
652        if (D) Log.d(TAG, "setHandle: " + handle + " - Type: " + type.name());
653        e.setHandle(handle, type);
654    }
655
656    private BluetoothMapMessageListingElement element(Cursor c, FilterInfo fi,
657        BluetoothMapAppParams ap) {
658        BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement();
659
660        setHandle(e, c, fi, ap);
661        setSubject(e, c, fi, ap);
662        setDateTime(e, c, fi, ap);
663        setSenderName(e, c, fi, ap);
664        setSenderAddressing(e, c, fi, ap);
665        setRecipientName(e, c, fi, ap);
666        setRecipientAddressing(e, c, fi, ap);
667        setType(e, c, fi, ap);
668        setSize(e, c, fi, ap);
669        setReceptionStatus(e, c, fi, ap);
670        setText(e, c, fi, ap);
671        setAttachmentSize(e, c, fi, ap);
672        setPriority(e, c, fi, ap);
673        setRead(e, c, fi, ap);
674        setSent(e, c, fi, ap);
675        setProtected(e, c, fi, ap);
676        return e;
677    }
678
679    private String getContactNameFromPhone(String phone) {
680        String name = null;
681
682        Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
683            Uri.encode(phone));
684
685        String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
686        String selection = Contacts.IN_VISIBLE_GROUP + "=1";
687        String orderBy = Contacts.DISPLAY_NAME + " ASC";
688
689        Cursor c = mResolver.query(uri, projection, selection, null, orderBy);
690
691        if (c != null && c.getCount() >= 1) {
692            c.moveToFirst();
693            name = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
694        }
695
696        c.close();
697        return name;
698    }
699
700    static public String getAddressMms(ContentResolver r, long id, int type) {
701        String selection = new String("msg_id=" + id + " AND type=" + type);
702        String uriStr = String.format("content://mms/%d/addr", id);
703        Uri uriAddress = Uri.parse(uriStr);
704        String addr = null;
705        Cursor c = r.query(uriAddress, null, selection, null, null);
706
707        if (c != null && c.moveToFirst()) {
708            addr = c.getString(c.getColumnIndex("address"));
709        }
710
711        if (c != null) {
712            c.close();
713        }
714        return addr;
715    }
716
717    private boolean matchRecipientMms(Cursor c, FilterInfo fi, String recip) {
718        boolean res;
719        long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
720        String phone = getAddressMms(mResolver, id, MMS_TO);
721        if (phone != null && phone.length() > 0) {
722            if (phone.matches(recip)) {
723                if (D) Log.d(TAG, "match recipient phone = " + phone);
724                res = true;
725            } else {
726                String name = getContactNameFromPhone(phone);
727                if (name != null && name.length() > 0 && name.matches(recip)) {
728                    if (D) Log.d(TAG, "match recipient name = " + name);
729                    res = true;
730                } else {
731                    res = false;
732                }
733            }
734        } else {
735            res = false;
736        }
737        return res;
738    }
739
740    private boolean matchRecipientSms(Cursor c, FilterInfo fi, String recip) {
741        boolean res;
742        int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
743        if (msgType == 1) {
744            String phone = fi.phoneNum;
745            String name = fi.phoneAlphaTag;
746            if (phone != null && phone.length() > 0 && phone.matches(recip)) {
747                if (D) Log.d(TAG, "match recipient phone = " + phone);
748                res = true;
749            } else if (name != null && name.length() > 0 && name.matches(recip)) {
750                if (D) Log.d(TAG, "match recipient name = " + name);
751                res = true;
752            } else {
753                res = false;
754            }
755        }
756        else {
757            String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
758            if (phone != null && phone.length() > 0) {
759                if (phone.matches(recip)) {
760                    if (D) Log.d(TAG, "match recipient phone = " + phone);
761                    res = true;
762                } else {
763                    String name = getContactNameFromPhone(phone);
764                    if (name != null && name.length() > 0 && name.matches(recip)) {
765                        if (D) Log.d(TAG, "match recipient name = " + name);
766                        res = true;
767                    } else {
768                        res = false;
769                    }
770                }
771            } else {
772                res = false;
773            }
774        }
775        return res;
776    }
777
778    private boolean matchRecipient(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
779        boolean res;
780        String recip = ap.getFilterRecipient();
781        if (recip != null && recip.length() > 0) {
782            recip = recip.replace("*", ".*");
783            recip = ".*" + recip + ".*";
784            if (fi.msgType == FilterInfo.TYPE_SMS) {
785                res = matchRecipientSms(c, fi, recip);
786            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
787                res = matchRecipientMms(c, fi, recip);
788            } else {
789                if (D) Log.d(TAG, "Unknown msg type: " + fi.msgType);
790                res = false;
791            }
792        } else {
793            res = true;
794        }
795        return res;
796    }
797
798    private boolean matchOriginatorMms(Cursor c, FilterInfo fi, String orig) {
799        boolean res;
800        long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
801        String phone = getAddressMms(mResolver, id, MMS_FROM);
802        if (phone != null && phone.length() > 0) {
803            if (phone.matches(orig)) {
804                if (D) Log.d(TAG, "match originator phone = " + phone);
805                res = true;
806            } else {
807                String name = getContactNameFromPhone(phone);
808                if (name != null && name.length() > 0 && name.matches(orig)) {
809                    if (D) Log.d(TAG, "match originator name = " + name);
810                    res = true;
811                } else {
812                    res = false;
813                }
814            }
815        } else {
816            res = false;
817        }
818        return res;
819    }
820
821    private boolean matchOriginatorSms(Cursor c, FilterInfo fi, String orig) {
822        boolean res;
823        int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
824        if (msgType == 1) {
825            String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
826            if (phone !=null && phone.length() > 0) {
827                if (phone.matches(orig)) {
828                    if (D) Log.d(TAG, "match originator phone = " + phone);
829                    res = true;
830                } else {
831                    String name = getContactNameFromPhone(phone);
832                    if (name != null && name.length() > 0 && name.matches(orig)) {
833                        if (D) Log.d(TAG, "match originator name = " + name);
834                        res = true;
835                    } else {
836                        res = false;
837                    }
838                }
839            } else {
840                res = false;
841            }
842        }
843        else {
844            String phone = fi.phoneNum;
845            String name = fi.phoneAlphaTag;
846            if (phone != null && phone.length() > 0 && phone.matches(orig)) {
847                if (D) Log.d(TAG, "match originator phone = " + phone);
848                res = true;
849            } else if (name != null && name.length() > 0 && name.matches(orig)) {
850                if (D) Log.d(TAG, "match originator name = " + name);
851                res = true;
852            } else {
853                res = false;
854            }
855        }
856        return res;
857    }
858
859   private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
860        boolean res;
861        String orig = ap.getFilterOriginator();
862        if (orig != null && orig.length() > 0) {
863            orig = orig.replace("*", ".*");
864            orig = ".*" + orig + ".*";
865            if (fi.msgType == FilterInfo.TYPE_SMS) {
866                res = matchOriginatorSms(c, fi, orig);
867            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
868                res = matchOriginatorMms(c, fi, orig);
869            } else {
870                Log.d(TAG, "Unknown msg type: " + fi.msgType);
871                res = false;
872            }
873        } else {
874            res = true;
875        }
876        return res;
877    }
878
879    private boolean matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
880        if (matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap)) {
881            return true;
882        } else {
883            return false;
884        }
885    }
886
887    private String setWhereFilterFolderTypeSms(String folder) {
888        String where = "";
889        if ("inbox".equalsIgnoreCase(folder)) {
890            where = "type = 1 AND thread_id <> -1";
891        }
892        else if ("outbox".equalsIgnoreCase(folder)) {
893            where = "(type = 4 OR type = 5 OR type = 6) AND thread_id <> -1";
894        }
895        else if ("sent".equalsIgnoreCase(folder)) {
896            where = "type = 2 AND thread_id <> -1";
897        }
898        else if ("draft".equalsIgnoreCase(folder)) {
899            where = "type = 3 AND thread_id <> -1";
900        }
901        else if ("deleted".equalsIgnoreCase(folder)) {
902            where = "thread_id = -1";
903        }
904
905        return where;
906    }
907
908    private String setWhereFilterFolderTypeMms(String folder) {
909        String where = "";
910        if ("inbox".equalsIgnoreCase(folder)) {
911            where = "msg_box = 1 AND thread_id <> -1";
912        }
913        else if ("outbox".equalsIgnoreCase(folder)) {
914            where = "msg_box = 4 AND thread_id <> -1";
915        }
916        else if ("sent".equalsIgnoreCase(folder)) {
917            where = "msg_box = 2 AND thread_id <> -1";
918        }
919        else if ("draft".equalsIgnoreCase(folder)) {
920            where = "msg_box = 3 AND thread_id <> -1";
921        }
922        else if ("deleted".equalsIgnoreCase(folder)) {
923            where = "thread_id = -1";
924        }
925
926        return where;
927    }
928
929    private String setWhereFilterFolderType(String folder, FilterInfo fi) {
930        String where = "";
931        if (fi.msgType == FilterInfo.TYPE_SMS) {
932            where = setWhereFilterFolderTypeSms(folder);
933        } else if (fi.msgType == FilterInfo.TYPE_MMS) {
934            where = setWhereFilterFolderTypeMms(folder);
935        }
936
937        return where;
938    }
939
940    private String setWhereFilterReadStatus(BluetoothMapAppParams ap) {
941        String where = "";
942        if (ap.getFilterReadStatus() != -1) {
943            if ((ap.getFilterReadStatus() & 0x01) != 0) {
944                where = " AND read=0 ";
945            }
946
947            if ((ap.getFilterReadStatus() & 0x02) != 0) {
948                where = " AND read=1 ";
949            }
950        }
951
952        return where;
953    }
954
955    private String setWhereFilterPeriod(BluetoothMapAppParams ap, FilterInfo fi) {
956        String where = "";
957        if ((ap.getFilterPeriodBegin() != -1)) {
958            if (fi.msgType == FilterInfo.TYPE_SMS) {
959            where = " AND date >= " + ap.getFilterPeriodBegin();
960            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
961                where = " AND date >= " + (ap.getFilterPeriodBegin() / 1000L);
962            }
963        }
964
965        if ((ap.getFilterPeriodEnd() != -1)) {
966            if (fi.msgType == FilterInfo.TYPE_SMS) {
967            where += " AND date < " + ap.getFilterPeriodEnd();
968            } else if (fi.msgType == FilterInfo.TYPE_MMS) {
969                where += " AND date < " + (ap.getFilterPeriodEnd() / 1000L);
970            }
971        }
972
973        return where;
974    }
975
976    private String setWhereFilterPhones(String str) {
977        String where = "";
978        str = str.replace("*", "%");
979
980        Cursor c = mResolver.query(ContactsContract.Contacts.CONTENT_URI, null,
981            ContactsContract.Contacts.DISPLAY_NAME + " like ?",
982            new String[]{str},
983            ContactsContract.Contacts.DISPLAY_NAME + " ASC");
984
985        while (c != null && c.moveToNext()) {
986            String contactId = c.getString(c.getColumnIndex(ContactsContract.Contacts._ID));
987
988            Cursor p = mResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
989                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
990                new String[]{contactId},
991                null);
992
993            while (p != null && p.moveToNext()) {
994                String number = p.getString(
995                    p.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
996
997                where += " address = " + "'" + number + "'";
998                if (!p.isLast()) {
999                    where += " OR ";
1000                }
1001            }
1002            if (!c.isLast()) {
1003                where += " OR ";
1004            }
1005            p.close();
1006        }
1007        c.close();
1008
1009        if (str != null && str.length() > 0) {
1010            if (where.length() > 0) {
1011                where += " OR ";
1012            }
1013            where += " address like " + "'" + str + "'";
1014        }
1015
1016        return where;
1017    }
1018
1019    private String setWhereFilterOriginator(BluetoothMapAppParams ap,
1020        FilterInfo fi) {
1021        String where = "";
1022        String orig = ap.getFilterOriginator();
1023
1024        if (orig != null && orig.length() > 0) {
1025            String phones = setWhereFilterPhones(orig);
1026
1027            if (phones.length() > 0) {
1028                where = " AND ((type <> 1) OR ( " + phones + " ))";
1029            } else {
1030                where = " AND (type <> 1)";
1031            }
1032
1033            orig = orig.replace("*", ".*");
1034            orig = ".*" + orig + ".*";
1035
1036            boolean localPhoneMatchOrig = false;
1037            if (fi.phoneNum != null && fi.phoneNum.length() > 0
1038                && fi.phoneNum.matches(orig)) {
1039                localPhoneMatchOrig = true;
1040            }
1041
1042            if (fi.phoneAlphaTag != null && fi.phoneAlphaTag.length() > 0
1043                && fi.phoneAlphaTag.matches(orig)) {
1044                localPhoneMatchOrig = true;
1045            }
1046
1047            if (!localPhoneMatchOrig) {
1048                where += " AND (type = 1)";
1049            }
1050        }
1051
1052        return where;
1053    }
1054
1055    private String setWhereFilterRecipient(BluetoothMapAppParams ap,
1056        FilterInfo fi) {
1057        String where = "";
1058        String recip = ap.getFilterRecipient();
1059
1060        if (recip != null && recip.length() > 0) {
1061            String phones = setWhereFilterPhones(recip);
1062
1063            if (phones.length() > 0) {
1064                where = " AND ((type = 1) OR ( " + phones + " ))";
1065            } else {
1066                where = " AND (type = 1)";
1067            }
1068
1069            recip = recip.replace("*", ".*");
1070            recip = ".*" + recip + ".*";
1071
1072            boolean localPhoneMatchOrig = false;
1073            if (fi.phoneNum != null && fi.phoneNum.length() > 0
1074                && fi.phoneNum.matches(recip)) {
1075                localPhoneMatchOrig = true;
1076            }
1077
1078            if (fi.phoneAlphaTag != null && fi.phoneAlphaTag.length() > 0
1079                && fi.phoneAlphaTag.matches(recip)) {
1080                localPhoneMatchOrig = true;
1081            }
1082
1083            if (!localPhoneMatchOrig) {
1084                where += " AND (type <> 1)";
1085            }
1086        }
1087
1088        return where;
1089    }
1090
1091    private String setWhereFilter(String folder, FilterInfo fi, BluetoothMapAppParams ap) {
1092        String where = "";
1093
1094        where += setWhereFilterFolderType(folder, fi);
1095        where += setWhereFilterReadStatus(ap);
1096        where += setWhereFilterPeriod(ap, fi);
1097        /* where += setWhereFilterOriginator(ap, fi); */
1098        /* where += setWhereFilterRecipient(ap, fi); */
1099
1100        if (D) Log.d(TAG, "where: " + where);
1101
1102        return where;
1103    }
1104
1105    private boolean smsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
1106        int msgType = ap.getFilterMessageType();
1107        int phoneType = fi.phoneType;
1108
1109        if (msgType == -1)
1110            return true;
1111        if ((msgType & 0x03) == 0)
1112            return true;
1113
1114        if (((msgType & 0x01) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
1115            return true;
1116
1117        if (((msgType & 0x02) == 0) && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
1118            return true;
1119
1120        return false;
1121    }
1122
1123    private boolean mmsSelected(FilterInfo fi, BluetoothMapAppParams ap) {
1124        int msgType = ap.getFilterMessageType();
1125
1126        if (msgType == -1)
1127            return true;
1128
1129        if ((msgType & 0x08) == 0)
1130            return true;
1131
1132        return false;
1133    }
1134
1135    private void setFilterInfo(FilterInfo fi) {
1136        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
1137        if (tm != null) {
1138            fi.phoneType = tm.getPhoneType();
1139            fi.phoneNum = tm.getLine1Number();
1140            fi.phoneAlphaTag = tm.getLine1AlphaTag();
1141            if (D) Log.d(TAG, "phone type = " + fi.phoneType +
1142                " phone num = " + fi.phoneNum +
1143                " phone alpha tag = " + fi.phoneAlphaTag);
1144        }
1145    }
1146
1147    public BluetoothMapMessageListing msgListing(String folder, BluetoothMapAppParams ap) {
1148        Log.d(TAG, "msgListing: folder = " + folder);
1149        BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
1150        BluetoothMapMessageListingElement e = null;
1151
1152        /* Cache some info used throughout filtering */
1153        FilterInfo fi = new FilterInfo();
1154        setFilterInfo(fi);
1155
1156        if (smsSelected(fi, ap)) {
1157            fi.msgType = FilterInfo.TYPE_SMS;
1158
1159            String where = setWhereFilter(folder, fi, ap);
1160
1161            Cursor c = mResolver.query(Sms.CONTENT_URI,
1162                SMS_PROJECTION, where, null, "date DESC");
1163
1164            if (c != null) {
1165                while (c.moveToNext()) {
1166                    if (matchAddresses(c, fi, ap)) {
1167                        printSms(c);
1168                        e = element(c, fi, ap);
1169                        bmList.add(e);
1170                    }
1171                }
1172                c.close();
1173            }
1174        }
1175
1176        if (mmsSelected(fi, ap)) {
1177            fi.msgType = FilterInfo.TYPE_MMS;
1178
1179            String where = setWhereFilter(folder, fi, ap);
1180
1181            Cursor c = mResolver.query(Mms.CONTENT_URI,
1182                MMS_PROJECTION, where, null, "date DESC");
1183
1184            if (c != null) {
1185                int cnt = 0;
1186                while (c.moveToNext()) {
1187                    if (matchAddresses(c, fi, ap)) {
1188                        printMms(c);
1189                        e = element(c, fi, ap);
1190                        bmList.add(e);
1191                    }
1192                }
1193                c.close();
1194            }
1195        }
1196
1197        /* Enable this if post sorting and segmenting needed */
1198        bmList.sort();
1199        bmList.segment(ap.getMaxListCount(), ap.getStartOffset());
1200
1201        return bmList;
1202    }
1203
1204    public int msgListingSize(String folder, BluetoothMapAppParams ap) {
1205        if (D) Log.d(TAG, "msgListingSize: folder = " + folder);
1206        int cnt = 0;
1207
1208        /* Cache some info used throughout filtering */
1209        FilterInfo fi = new FilterInfo();
1210        setFilterInfo(fi);
1211
1212        if (smsSelected(fi, ap)) {
1213            fi.msgType = FilterInfo.TYPE_SMS;
1214            String where = setWhereFilter(folder, fi, ap);
1215            Cursor c = mResolver.query(Sms.CONTENT_URI,
1216                SMS_PROJECTION, where, null, "date DESC");
1217
1218            if (c != null) {
1219                cnt = c.getCount();
1220                c.close();
1221            }
1222        }
1223
1224        if (mmsSelected(fi, ap)) {
1225            fi.msgType = FilterInfo.TYPE_MMS;
1226            String where = setWhereFilter(folder, fi, ap);
1227            Cursor c = mResolver.query(Mms.CONTENT_URI,
1228                MMS_PROJECTION, where, null, "date DESC");
1229
1230            if (c != null) {
1231                cnt += c.getCount();
1232                c.close();
1233            }
1234        }
1235
1236        if (D) Log.d(TAG, "msgListingSize: size = " + cnt);
1237        return cnt;
1238    }
1239    /**
1240     * Return true if there are unread messages in the requested list of messages
1241     * @param folder folder where the message listing should come from
1242     * @param ap application parameter object
1243     * @return true if unread messages are in the list, else false
1244     */
1245    public boolean msgListingHasUnread(String folder, BluetoothMapAppParams ap) {
1246        if (D) Log.d(TAG, "msgListingHasUnread: folder = " + folder);
1247        int cnt = 0;
1248
1249        /* Cache some info used throughout filtering */
1250        FilterInfo fi = new FilterInfo();
1251        setFilterInfo(fi);
1252
1253        if (smsSelected(fi, ap)) {
1254            fi.msgType = FilterInfo.TYPE_SMS;
1255            String where = setWhereFilterFolderType(folder, fi);
1256            where += " AND read=0 ";
1257            where += setWhereFilterPeriod(ap, fi);
1258            Cursor c = mResolver.query(Sms.CONTENT_URI,
1259                SMS_PROJECTION, where, null, "date DESC");
1260
1261            if (c != null) {
1262                cnt = c.getCount();
1263                c.close();
1264            }
1265        }
1266
1267        if (mmsSelected(fi, ap)) {
1268            fi.msgType = FilterInfo.TYPE_MMS;
1269            String where = setWhereFilterFolderType(folder, fi);
1270            where += " AND read=0 ";
1271            where += setWhereFilterPeriod(ap, fi);
1272            Cursor c = mResolver.query(Mms.CONTENT_URI,
1273                MMS_PROJECTION, where, null, "date DESC");
1274
1275            if (c != null) {
1276                cnt += c.getCount();
1277                c.close();
1278            }
1279        }
1280
1281        if (D) Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt);
1282        return (cnt>0)?true:false;
1283    }
1284
1285    /**
1286     * Get the folder name of an SMS message or MMS message.
1287     * @param c the cursor pointing at the message
1288     * @return the folder name.
1289     */
1290    private String getFolderName(int type, int threadId) {
1291
1292        if(threadId == -1)
1293            return "deleted";
1294
1295        switch(type) {
1296        case 1:
1297            return "inbox";
1298        case 2:
1299            return "sent";
1300        case 3:
1301            return "draft";
1302        case 4: // Just name outbox, failed and queued "outbox"
1303        case 5:
1304        case 6:
1305            return "outbox";
1306        }
1307        return "";
1308    }
1309
1310    public byte[] getMessage(String handle, BluetoothMapAppParams appParams) throws UnsupportedEncodingException{
1311        TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
1312        long id = BluetoothMapUtils.getCpHandle(handle);
1313        switch(type) {
1314        case SMS_GSM:
1315        case SMS_CDMA:
1316            return getSmsMessage(id, appParams.getCharset());
1317        case MMS:
1318            return getMmsMessage(id, appParams);
1319        case EMAIL:
1320            throw new IllegalArgumentException("Email not implemented - invalid message handle.");
1321        }
1322        throw new IllegalArgumentException("Invalid message handle.");
1323    }
1324
1325    private void setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone, boolean incoming) {
1326        String contactId = null, contactName = null;
1327        String[] phoneNumbers = null;
1328        String[] emailAddresses = null;
1329        Cursor p;
1330
1331        Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
1332                Uri.encode(phone));
1333
1334        String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
1335        String selection = Contacts.IN_VISIBLE_GROUP + "=1";
1336        String orderBy = Contacts._ID + " ASC";
1337
1338        // Get the contact _ID and name
1339        p = mResolver.query(uri, projection, selection, null, orderBy);
1340        if (p != null && p.getCount() >= 1) {
1341            p.moveToFirst();
1342            contactId = p.getString(p.getColumnIndex(Contacts._ID));
1343            contactName = p.getString(p.getColumnIndex(Contacts.DISPLAY_NAME));
1344        }
1345        p.close();
1346
1347        // Bail out if we are unable to find a contact, based on the phone number
1348        if(contactId == null) {
1349            phoneNumbers = new String[1];
1350            phoneNumbers[0] = phone;
1351        }
1352        else {
1353            // Fetch all contact phone numbers
1354            p = mResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
1355                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
1356                new String[]{contactId},
1357                null);
1358            if(p != null) {
1359                int i = 0;
1360                phoneNumbers = new String[p.getCount()];
1361                while (p != null && p.moveToNext()) {
1362                    String number = p.getString(
1363                        p.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
1364                    phoneNumbers[i++] = number;
1365                }
1366                p.close();
1367            }
1368
1369            // Fetch contact e-mail addresses
1370            p = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
1371                    ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
1372                    new String[]{contactId},
1373                    null);
1374            if(p != null) {
1375                int i = 0;
1376                emailAddresses = new String[p.getCount()];
1377                while (p != null && p.moveToNext()) {
1378                    String emailAddress = p.getString(
1379                        p.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS));
1380                    emailAddresses[i++] = emailAddress;
1381                }
1382                p.close();
1383            }
1384        }
1385        if(incoming == true)
1386            message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
1387        else
1388            message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses); // Use version 3.0 as we only have a formatted name
1389    }
1390
1391    public static final int MAP_MESSAGE_CHARSET_NATIVE = 0;
1392    public static final int MAP_MESSAGE_CHARSET_UTF8 = 1;
1393
1394    public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException{
1395        int type, threadId;
1396        long time = -1;
1397        String msgBody;
1398        BluetoothMapbMessageSms message = new BluetoothMapbMessageSms();
1399        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
1400        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null);
1401
1402        if(c != null && c.moveToFirst())
1403        {
1404
1405            if(V) Log.d(TAG,"c.count: " + c.getCount());
1406
1407            if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
1408                message.setType(TYPE.SMS_GSM);
1409            } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1410                message.setType(TYPE.SMS_CDMA);
1411            }
1412
1413            String read = c.getString(c.getColumnIndex(Sms.READ));
1414            if (read.equalsIgnoreCase("1"))
1415                message.setStatus(true);
1416            else
1417                message.setStatus(false);
1418
1419            type = c.getInt(c.getColumnIndex(Sms.TYPE));
1420            threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
1421            message.setFolder(getFolderName(type, threadId));
1422
1423            msgBody = c.getString(c.getColumnIndex(Sms.BODY));
1424
1425            String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
1426
1427            time = c.getLong(c.getColumnIndex(Sms.DATE));
1428            if(type == 1) // Inbox message needs to set the vCard as originator
1429                setVCardFromPhoneNumber(message, phone, true);
1430            else          // Other messages sets the vCard as the recipient
1431                setVCardFromPhoneNumber(message, phone, false);
1432
1433            if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
1434                if(type == 1) //Inbox
1435                    message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time));
1436                else
1437                    message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
1438            } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
1439                message.setSmsBody(msgBody);
1440            }
1441
1442            c.close();
1443
1444            return message.encode();
1445        }
1446        throw new IllegalArgumentException("SMS handle not found");
1447    }
1448
1449    private void extractMmsAddresses(long id, BluetoothMapbMessageMmsEmail message) {
1450        final String[] projection = null;
1451        String selection = new String("msg_id=" + id);
1452        String uriStr = String.format("content://mms/%d/addr", id);
1453        Uri uriAddress = Uri.parse(uriStr);
1454        Cursor c = mResolver.query(
1455            uriAddress,
1456            projection,
1457            selection,
1458            null, null);
1459        /* TODO: Change the setVCard...() to return the vCard, and use the name in message.addXxx() */
1460        if (c.moveToFirst()) {
1461            do {
1462                String address = c.getString(c.getColumnIndex("address"));
1463                Integer type = c.getInt(c.getColumnIndex("type"));
1464                switch(type) {
1465                case MMS_FROM:
1466                    setVCardFromPhoneNumber(message, address, true);
1467                    message.addFrom(null, address);
1468                    break;
1469                case MMS_TO:
1470                    setVCardFromPhoneNumber(message, address, false);
1471                    message.addTo(null, address);
1472                    break;
1473                case MMS_CC:
1474                    setVCardFromPhoneNumber(message, address, false);
1475                    message.addCc(null, address);
1476                    break;
1477                case MMS_BCC:
1478                    setVCardFromPhoneNumber(message, address, false);
1479                    message.addBcc(null, address);
1480                default:
1481                    break;
1482                }
1483            } while(c.moveToNext());
1484        }
1485    }
1486
1487    /**
1488     * Read out a mms data part and return the data in a byte array.
1489     * @param partid the content provider id of the mms.
1490     * @return
1491     */
1492    private byte[] readMmsDataPart(long partid) {
1493        String uriStr = String.format("content://mms/part/%d", partid);
1494        Uri uriAddress = Uri.parse(uriStr);
1495        InputStream is = null;
1496        ByteArrayOutputStream os = new ByteArrayOutputStream();
1497        int bufferSize = 8192;
1498        byte[] buffer = new byte[bufferSize];
1499        byte[] retVal = null;
1500
1501        try {
1502            is = mResolver.openInputStream(uriAddress);
1503            int len = 0;
1504            while ((len = is.read(buffer)) != -1) {
1505              os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
1506            }
1507            retVal = os.toByteArray();
1508        } catch (IOException e) {
1509            // do nothing for now
1510            Log.w(TAG,"Error reading part data",e);
1511        } finally {
1512            try {
1513                os.close();
1514                is.close();
1515            } catch (IOException e) {
1516            }
1517        }
1518        return retVal;
1519    }
1520
1521    /**
1522     * Read out the mms parts and update the bMessage object provided i {@linkplain message}
1523     * @param id the content provider ID of the message
1524     * @param message the bMessage object to add the information to
1525     */
1526    private void extractMmsParts(long id, BluetoothMapbMessageMmsEmail message)
1527    {
1528        /* TODO: If the attachment appParam is set to "no", only add the text parts.
1529         * (content type contains "text" - case insensitive) */
1530        final String[] projection = null;
1531        String selection = new String("mid=" + id);
1532        String uriStr = String.format("content://mms/%d/part", id);
1533        Uri uriAddress = Uri.parse(uriStr);
1534        BluetoothMapbMessageMmsEmail.MimePart part;
1535        Cursor c = mResolver.query(
1536            uriAddress,
1537            projection,
1538            selection,
1539            null, null);
1540
1541        if (c.moveToFirst()) {
1542            do {
1543                Long partId = c.getLong(c.getColumnIndex(BaseColumns._ID));
1544                String contentType = c.getString(c.getColumnIndex("ct"));
1545                String name = c.getString(c.getColumnIndex("name"));
1546                String charset = c.getString(c.getColumnIndex("chset"));
1547                String filename = c.getString(c.getColumnIndex("fn"));
1548                String text = c.getString(c.getColumnIndex("text"));
1549                Integer fd = c.getInt(c.getColumnIndex("_data"));
1550                String cid = c.getString(c.getColumnIndex("cid"));
1551                String cl = c.getString(c.getColumnIndex("cl"));
1552                String cdisp = c.getString(c.getColumnIndex("cd"));
1553
1554                if(D) Log.d(TAG, "     _id : " + partId +
1555                        "\n     ct : " + contentType +
1556                        "\n     partname : " + name +
1557                        "\n     charset : " + charset +
1558                        "\n     filename : " + filename +
1559                        "\n     text : " + text +
1560                        "\n     fd : " + fd +
1561                        "\n     cid : " + cid +
1562                        "\n     cl : " + cl +
1563                        "\n     cdisp : " + cdisp);
1564
1565                part = message.addMimePart();
1566                part.contentType = contentType;
1567                part.partName = name;
1568                part.contentId = cid;
1569                part.contentLocation = cl;
1570                part.contentDisposition = cdisp;
1571
1572                try {
1573                    if(text != null) {
1574                        part.data = text.getBytes("UTF-8");
1575                        part.charsetName = "utf-8";
1576                    }
1577                    else {
1578                        part.data = readMmsDataPart(partId);
1579                        if(charset != null)
1580                            part.charsetName = CharacterSets.getMimeName(Integer.parseInt(charset));
1581                    }
1582                } catch (NumberFormatException e) {
1583                    Log.d(TAG,"extractMmsParts",e);
1584                    part.data = null;
1585                    part.charsetName = null;
1586                } catch (UnsupportedEncodingException e) {
1587                    Log.d(TAG,"extractMmsParts",e);
1588                    part.data = null;
1589                    part.charsetName = null;
1590                } finally {
1591                }
1592                part.fileName = filename;
1593            } while(c.moveToNext());
1594        }
1595        message.updateCharset();
1596    }
1597
1598    /**
1599     *
1600     * @param id the content provider id for the message to fetch.
1601     * @param appParams The application parameter object received from the client.
1602     * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
1603     * @throws UnsupportedEncodingException if UTF-8 is not supported,
1604     * which is guaranteed to be supported on an android device
1605     */
1606    public byte[] getMmsMessage(long id, BluetoothMapAppParams appParams) throws UnsupportedEncodingException {
1607        int msgBox, threadId;
1608        BluetoothMapbMessageMmsEmail message = new BluetoothMapbMessageMmsEmail();
1609        Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null);
1610        if(c != null && c.moveToFirst())
1611        {
1612            message.setType(TYPE.MMS);
1613
1614            // The MMS info:
1615            String read = c.getString(c.getColumnIndex(Mms.READ));
1616            if (read.equalsIgnoreCase("1"))
1617                message.setStatus(true);
1618            else
1619                message.setStatus(false);
1620
1621            msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
1622            threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
1623            message.setFolder(getFolderName(msgBox, threadId));
1624
1625            message.setSubject(c.getString(c.getColumnIndex(Mms.SUBJECT)));
1626            message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
1627            message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
1628            message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
1629            message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true); // - TODO: Do we need this - yes, if we have only text, we should not make this a multipart message
1630            message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
1631            // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
1632            // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
1633
1634            // The parts
1635            extractMmsParts(id, message);
1636
1637            // The addresses
1638            extractMmsAddresses(id, message);
1639
1640            c.close();
1641
1642            return message.encode();
1643        }
1644        else if(c != null) {
1645            c.close();
1646        }
1647
1648        throw new IllegalArgumentException("MMS handle not found");
1649    }
1650
1651}
1652