BluetoothPbapObexServer.java revision 76dc9e2d09ea7f2655e557462c149a0c6b6dd771
1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.android.bluetooth.pbap;
34
35import android.content.Context;
36import android.os.Message;
37import android.os.Handler;
38import android.text.TextUtils;
39import android.util.Log;
40import android.provider.CallLog.Calls;
41import android.provider.CallLog;
42
43import java.io.IOException;
44import java.io.OutputStream;
45import java.text.CharacterIterator;
46import java.text.StringCharacterIterator;
47import java.util.ArrayList;
48import java.util.Arrays;
49
50import javax.obex.ServerRequestHandler;
51import javax.obex.ResponseCodes;
52import javax.obex.ApplicationParameter;
53import javax.obex.ServerOperation;
54import javax.obex.Operation;
55import javax.obex.HeaderSet;
56
57public class BluetoothPbapObexServer extends ServerRequestHandler {
58
59    private static final String TAG = "BluetoothPbapObexServer";
60
61    private static final boolean D = BluetoothPbapService.DEBUG;
62
63    private static final boolean V = BluetoothPbapService.VERBOSE;
64
65    private static final int UUID_LENGTH = 16;
66
67    // The length of suffix of vcard name - ".vcf" is 5
68    private static final int VCARD_NAME_SUFFIX_LENGTH = 5;
69
70    // 128 bit UUID for PBAP
71    private static final byte[] PBAP_TARGET = new byte[] {
72            0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66,
73            0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66
74    };
75
76    // Currently not support SIM card
77    private static final String[] LEGAL_PATH = {
78            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
79            "/telecom/cch"
80    };
81
82    @SuppressWarnings("unused")
83    private static final String[] LEGAL_PATH_WITH_SIM = {
84            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
85            "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och",
86            "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb"
87
88    };
89
90    // SIM card
91    private static final String SIM1 = "SIM1";
92
93    // missed call history
94    private static final String MCH = "mch";
95
96    // incoming call history
97    private static final String ICH = "ich";
98
99    // outgoing call history
100    private static final String OCH = "och";
101
102    // combined call history
103    private static final String CCH = "cch";
104
105    // phone book
106    private static final String PB = "pb";
107
108    private static final String ICH_PATH = "/telecom/ich";
109
110    private static final String OCH_PATH = "/telecom/och";
111
112    private static final String MCH_PATH = "/telecom/mch";
113
114    private static final String CCH_PATH = "/telecom/cch";
115
116    private static final String PB_PATH = "/telecom/pb";
117
118    // type for list vcard objects
119    private static final String TYPE_LISTING = "x-bt/vcard-listing";
120
121    // type for get single vcard object
122    private static final String TYPE_VCARD = "x-bt/vcard";
123
124    // to indicate if need send body besides headers
125    private static final int NEED_SEND_BODY = -1;
126
127    // type for download all vcard objects
128    private static final String TYPE_PB = "x-bt/phonebook";
129
130    // The number of indexes in the phone book.
131    private boolean mNeedPhonebookSize = false;
132
133    // The number of missed calls that have not been checked on the PSE at the
134    // point of the request. Only apply to "mch" case.
135    private boolean mNeedNewMissedCallsNum = false;
136
137    private int mMissedCallSize = 0;
138
139    // record current path the client are browsing
140    private String mCurrentPath = "";
141
142    private Handler mCallback = null;
143
144    private Context mContext;
145
146    private BluetoothPbapVcardManager mVcardManager;
147
148    private int mOrderBy  = ORDER_BY_INDEXED;
149
150    private static int CALLLOG_NUM_LIMIT = 50;
151
152    public static int ORDER_BY_INDEXED = 0;
153
154    public static int ORDER_BY_ALPHABETICAL = 1;
155
156    public static boolean sIsAborted = false;
157
158    public static class ContentType {
159        public static final int PHONEBOOK = 1;
160
161        public static final int INCOMING_CALL_HISTORY = 2;
162
163        public static final int OUTGOING_CALL_HISTORY = 3;
164
165        public static final int MISSED_CALL_HISTORY = 4;
166
167        public static final int COMBINED_CALL_HISTORY = 5;
168    }
169
170    public BluetoothPbapObexServer(Handler callback, Context context) {
171        super();
172        mCallback = callback;
173        mContext = context;
174        mVcardManager = new BluetoothPbapVcardManager(mContext);
175    }
176
177    @Override
178    public int onConnect(final HeaderSet request, HeaderSet reply) {
179        if (V) logHeader(request);
180        notifyUpdateWakeLock();
181        try {
182            byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
183            if (uuid == null) {
184                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
185            }
186            if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
187
188            if (uuid.length != UUID_LENGTH) {
189                Log.w(TAG, "Wrong UUID length");
190                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
191            }
192            for (int i = 0; i < UUID_LENGTH; i++) {
193                if (uuid[i] != PBAP_TARGET[i]) {
194                    Log.w(TAG, "Wrong UUID");
195                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
196                }
197            }
198            reply.setHeader(HeaderSet.WHO, uuid);
199        } catch (IOException e) {
200            Log.e(TAG, e.toString());
201            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
202        }
203
204        try {
205            byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
206            if (remote != null) {
207                if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
208                reply.setHeader(HeaderSet.TARGET, remote);
209            }
210        } catch (IOException e) {
211            Log.e(TAG, e.toString());
212            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
213        }
214
215        if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
216                "MSG_SESSION_ESTABLISHED msg.");
217
218        Message msg = Message.obtain(mCallback);
219        msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
220        msg.sendToTarget();
221
222        return ResponseCodes.OBEX_HTTP_OK;
223    }
224
225    @Override
226    public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
227        if (D) Log.d(TAG, "onDisconnect(): enter");
228        if (V) logHeader(req);
229        notifyUpdateWakeLock();
230        resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
231        if (mCallback != null) {
232            Message msg = Message.obtain(mCallback);
233            msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED;
234            msg.sendToTarget();
235            if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
236        }
237    }
238
239    @Override
240    public int onAbort(HeaderSet request, HeaderSet reply) {
241        if (D) Log.d(TAG, "onAbort(): enter.");
242        notifyUpdateWakeLock();
243        sIsAborted = true;
244        return ResponseCodes.OBEX_HTTP_OK;
245    }
246
247    @Override
248    public int onPut(final Operation op) {
249        if (D) Log.d(TAG, "onPut(): not support PUT request.");
250        notifyUpdateWakeLock();
251        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
252    }
253
254    @Override
255    public int onDelete(final HeaderSet request, final HeaderSet reply) {
256        if (D) Log.d(TAG, "onDelete(): not support PUT request.");
257        notifyUpdateWakeLock();
258        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
259    }
260
261    @Override
262    public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
263            final boolean create) {
264        if (V) logHeader(request);
265        if (D) Log.d(TAG, "before setPath, mCurrentPath ==  " + mCurrentPath);
266        notifyUpdateWakeLock();
267        String current_path_tmp = mCurrentPath;
268        String tmp_path = null;
269        try {
270            tmp_path = (String)request.getHeader(HeaderSet.NAME);
271        } catch (IOException e) {
272            Log.e(TAG, "Get name header fail");
273            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
274        }
275        if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmp_path);
276
277        if (backup) {
278            if (current_path_tmp.length() != 0) {
279                current_path_tmp = current_path_tmp.substring(0,
280                        current_path_tmp.lastIndexOf("/"));
281            }
282        } else {
283            if (tmp_path == null) {
284                current_path_tmp = "";
285            } else {
286                current_path_tmp = current_path_tmp + "/" + tmp_path;
287            }
288        }
289
290        if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) {
291            if (create) {
292                Log.w(TAG, "path create is forbidden!");
293                return ResponseCodes.OBEX_HTTP_FORBIDDEN;
294            } else {
295                Log.w(TAG, "path is not legal");
296                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
297            }
298        }
299        mCurrentPath = current_path_tmp;
300        if (V) Log.v(TAG, "after setPath, mCurrentPath ==  " + mCurrentPath);
301
302        return ResponseCodes.OBEX_HTTP_OK;
303    }
304
305    @Override
306    public void onClose() {
307        if (mCallback != null) {
308            Message msg = Message.obtain(mCallback);
309            msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
310            msg.sendToTarget();
311            if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
312        }
313    }
314
315    @Override
316    public int onGet(Operation op) {
317        notifyUpdateWakeLock();
318        sIsAborted = false;
319        HeaderSet request = null;
320        HeaderSet reply = new HeaderSet();
321        String type = "";
322        String name = "";
323        byte[] appParam = null;
324        AppParamValue appParamValue = new AppParamValue();
325        try {
326            request = op.getReceivedHeader();
327            type = (String)request.getHeader(HeaderSet.TYPE);
328            name = (String)request.getHeader(HeaderSet.NAME);
329            appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
330        } catch (IOException e) {
331            Log.e(TAG, "request headers error");
332            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
333        }
334
335        if (V) logHeader(request);
336        if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name);
337
338        if (type == null) {
339            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
340        }
341        // Accroding to specification,the name header could be omitted such as
342        // sony erriccsonHBH-DS980
343
344        // For "x-bt/phonebook" and "x-bt/vcard-listing":
345        // if name == null, guess what carkit actually want from current path
346        // For "x-bt/vcard":
347        // We decide which kind of content client would like per current path
348
349        boolean validName = true;
350        if (TextUtils.isEmpty(name)) {
351            validName = false;
352        }
353
354        if (!validName || (validName && type.equals(TYPE_VCARD))) {
355            if (D) Log.d(TAG, "Guess what carkit actually want from current path (" +
356                    mCurrentPath + ")");
357
358            if (mCurrentPath.equals(PB_PATH)) {
359                appParamValue.needTag = ContentType.PHONEBOOK;
360            } else if (mCurrentPath.equals(ICH_PATH)) {
361                appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
362            } else if (mCurrentPath.equals(OCH_PATH)) {
363                appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
364            } else if (mCurrentPath.equals(MCH_PATH)) {
365                appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
366                mNeedNewMissedCallsNum = true;
367            } else if (mCurrentPath.equals(CCH_PATH)) {
368                appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
369            } else {
370                Log.w(TAG, "mCurrentpath is not valid path!!!");
371                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
372            }
373            if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
374        } else {
375            // Not support SIM card currently
376            if (name.contains(SIM1.subSequence(0, SIM1.length()))) {
377                Log.w(TAG, "Not support access SIM card info!");
378                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
379            }
380
381            // we have weak name checking here to provide better
382            // compatibility with other devices,although unique name such as
383            // "pb.vcf" is required by SIG spec.
384            if (isNameMatchTarget(name, PB)) {
385                appParamValue.needTag = ContentType.PHONEBOOK;
386                if (D) Log.v(TAG, "download phonebook request");
387            } else if (isNameMatchTarget(name, ICH)) {
388                appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
389                if (D) Log.v(TAG, "download incoming calls request");
390            } else if (isNameMatchTarget(name, OCH)) {
391                appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
392                if (D) Log.v(TAG, "download outgoing calls request");
393            } else if (isNameMatchTarget(name, MCH)) {
394                appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
395                mNeedNewMissedCallsNum = true;
396                if (D) Log.v(TAG, "download missed calls request");
397            } else if (isNameMatchTarget(name, CCH)) {
398                appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
399                if (D) Log.v(TAG, "download combined calls request");
400            } else {
401                Log.w(TAG, "Input name doesn't contain valid info!!!");
402                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
403            }
404        }
405
406        if ((appParam != null) && !parseApplicationParameter(appParam, appParamValue)) {
407            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
408        }
409
410        // listing request
411        if (type.equals(TYPE_LISTING)) {
412            return pullVcardListing(appParam, appParamValue, reply, op);
413        }
414        // pull vcard entry request
415        else if (type.equals(TYPE_VCARD)) {
416            return pullVcardEntry(appParam, appParamValue, op, name, mCurrentPath);
417        }
418        // down load phone book request
419        else if (type.equals(TYPE_PB)) {
420            return pullPhonebook(appParam, appParamValue, reply, op, name);
421        } else {
422            Log.w(TAG, "unknown type request!!!");
423            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
424        }
425    }
426
427    private boolean isNameMatchTarget(String name, String target) {
428        String contentTypeName = name;
429        if (contentTypeName.endsWith(".vcf")) {
430            contentTypeName = contentTypeName
431                    .substring(0, contentTypeName.length() - ".vcf".length());
432        }
433        // There is a test case: Client will send a wrong name "/telecom/pbpb".
434        // So we must use the String between '/' and '/' as a indivisible part
435        // for comparing.
436        String[] nameList = contentTypeName.split("/");
437        for (String subName : nameList) {
438            if (subName.equals(target)) {
439                return true;
440            }
441        }
442        return false;
443    }
444
445    /** check whether path is legal */
446    private final boolean isLegalPath(final String str) {
447        if (str.length() == 0) {
448            return true;
449        }
450        for (int i = 0; i < LEGAL_PATH.length; i++) {
451            if (str.equals(LEGAL_PATH[i])) {
452                return true;
453            }
454        }
455        return false;
456    }
457
458    private class AppParamValue {
459        public int maxListCount;
460
461        public int listStartOffset;
462
463        public String searchValue;
464
465        // Indicate which vCard parameter the search operation shall be carried
466        // out on. Can be "Name | Number | Sound", default value is "Name".
467        public String searchAttr;
468
469        // Indicate which sorting order shall be used for the
470        // <x-bt/vcard-listing> listing object.
471        // Can be "Alphabetical | Indexed | Phonetical", default value is
472        // "Indexed".
473        public String order;
474
475        public int needTag;
476
477        public boolean vcard21;
478
479        public byte[] filter;
480
481        public boolean ignorefilter;
482
483        public AppParamValue() {
484            maxListCount = 0xFFFF;
485            listStartOffset = 0;
486            searchValue = "";
487            searchAttr = "";
488            order = "";
489            needTag = 0x00;
490            vcard21 = true;
491            //Filter is not set by default
492            ignorefilter = true;
493            filter = new byte[] {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} ;
494        }
495
496        public void dump() {
497            Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
498                    + " searchValue=" + searchValue + " searchAttr=" + searchAttr + " needTag="
499                    + needTag + " vcard21=" + vcard21 + " order=" + order);
500        }
501    }
502
503    /** To parse obex application parameter */
504    private final boolean parseApplicationParameter(final byte[] appParam,
505            AppParamValue appParamValue) {
506        int i = 0;
507        boolean parseOk = true;
508        while ((i < appParam.length) && (parseOk == true)) {
509            switch (appParam[i]) {
510                case ApplicationParameter.TRIPLET_TAGID.FILTER_TAGID:
511                    i += 2; // length and tag field in triplet
512                    for (int index=0; index < ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH;
513                         index++) {
514                        if (appParam[i+index] != 0){
515                            appParamValue.ignorefilter = false;
516                            appParamValue.filter[index] = appParam[i+index];
517                        }
518                    }
519                    i += ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH;
520                    break;
521                case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID:
522                    i += 2; // length and tag field in triplet
523                    appParamValue.order = Byte.toString(appParam[i]);
524                    i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH;
525                    break;
526                case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID:
527                    i += 1; // length field in triplet
528                    // length of search value is variable
529                    int length = appParam[i];
530                    if (length == 0) {
531                        parseOk = false;
532                        break;
533                    }
534                    if (appParam[i+length] == 0x0) {
535                        appParamValue.searchValue = new String(appParam, i + 1, length-1);
536                    } else {
537                        appParamValue.searchValue = new String(appParam, i + 1, length);
538                    }
539                    i += length;
540                    i += 1;
541                    break;
542                case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
543                    i += 2;
544                    appParamValue.searchAttr = Byte.toString(appParam[i]);
545                    i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
546                    break;
547                case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
548                    i += 2;
549                    if (appParam[i] == 0 && appParam[i + 1] == 0) {
550                        mNeedPhonebookSize = true;
551                    } else {
552                        int highValue = appParam[i] & 0xff;
553                        int lowValue = appParam[i + 1] & 0xff;
554                        appParamValue.maxListCount = highValue * 256 + lowValue;
555                    }
556                    i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
557                    break;
558                case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
559                    i += 2;
560                    int highValue = appParam[i] & 0xff;
561                    int lowValue = appParam[i + 1] & 0xff;
562                    appParamValue.listStartOffset = highValue * 256 + lowValue;
563                    i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
564                    break;
565                case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
566                    i += 2;// length field in triplet
567                    if (appParam[i] != 0) {
568                        appParamValue.vcard21 = false;
569                    }
570                    i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
571                    break;
572                default:
573                    parseOk = false;
574                    Log.e(TAG, "Parse Application Parameter error");
575                    break;
576            }
577        }
578
579        if (D) appParamValue.dump();
580
581        return parseOk;
582    }
583
584    /** Form and Send an XML format String to client for Phone book listing */
585    private final int sendVcardListingXml(final int type, Operation op,
586            final int maxListCount, final int listStartOffset, final String searchValue,
587            String searchAttr) {
588        StringBuilder result = new StringBuilder();
589        int itemsFound = 0;
590        result.append("<?xml version=\"1.0\"?>");
591        result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
592        result.append("<vCard-listing version=\"1.0\">");
593
594        // Phonebook listing request
595        if (type == ContentType.PHONEBOOK) {
596            if (searchAttr.equals("0")) { // search by name
597                itemsFound = createList(maxListCount, listStartOffset, searchValue, result,
598                        "name");
599            } else if (searchAttr.equals("1")) { // search by number
600                itemsFound = createList(maxListCount, listStartOffset, searchValue, result,
601                        "number");
602            }// end of search by number
603            else {
604                return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
605            }
606        }
607        // Call history listing request
608        else {
609            ArrayList<String> nameList = mVcardManager.loadCallHistoryList(type);
610            int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size();
611            int startPoint = listStartOffset;
612            int endPoint = startPoint + requestSize;
613            if (endPoint > nameList.size()) {
614                endPoint = nameList.size();
615            }
616            if (D) Log.d(TAG, "call log list, size=" + requestSize + " offset=" + listStartOffset);
617
618            for (int j = startPoint; j < endPoint; j++) {
619                writeVCardEntry(j+1, nameList.get(j),result);
620            }
621        }
622        result.append("</vCard-listing>");
623
624        if (V) Log.v(TAG, "itemsFound =" + itemsFound);
625
626        return pushBytes(op, result.toString());
627    }
628
629    private int createList(final int maxListCount, final int listStartOffset,
630            final String searchValue, StringBuilder result, String type) {
631        int itemsFound = 0;
632        ArrayList<String> nameList = mVcardManager.getPhonebookNameList(mOrderBy);
633        final int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size();
634        final int listSize = nameList.size();
635        String compareValue = "", currentValue;
636
637        if (D) Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset="
638                    + listStartOffset + " searchValue=" + searchValue);
639
640        if (type.equals("number")) {
641            // query the number, to get the names
642            ArrayList<String> names = mVcardManager.getContactNamesByNumber(searchValue);
643            for (int i = 0; i < names.size(); i++) {
644                compareValue = names.get(i).trim();
645                if (D) Log.d(TAG, "compareValue=" + compareValue);
646                for (int pos = listStartOffset; pos < listSize &&
647                        itemsFound < requestSize; pos++) {
648                    currentValue = nameList.get(pos);
649                    if (D) Log.d(TAG, "currentValue=" + currentValue);
650                    if (currentValue.equals(compareValue)) {
651                        itemsFound++;
652                        if (currentValue.contains(","))
653                           currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
654                        writeVCardEntry(pos, currentValue,result);
655                    }
656                }
657                if (itemsFound >= requestSize) {
658                    break;
659                }
660            }
661        } else {
662            if (searchValue != null) {
663                compareValue = searchValue.trim();
664            }
665            for (int pos = listStartOffset; pos < listSize &&
666                    itemsFound < requestSize; pos++) {
667                currentValue = nameList.get(pos);
668                if (currentValue.contains(","))
669                    currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
670
671                if (searchValue.isEmpty() || ((currentValue.toLowerCase()).equals(compareValue.toLowerCase()))) {
672                    itemsFound++;
673                    writeVCardEntry(pos, currentValue,result);
674                }
675            }
676        }
677        return itemsFound;
678    }
679
680    /**
681     * Function to send obex header back to client such as get phonebook size
682     * request
683     */
684    private final int pushHeader(final Operation op, final HeaderSet reply) {
685        OutputStream outputStream = null;
686
687        if (D) Log.d(TAG, "Push Header");
688        if (D) Log.d(TAG, reply.toString());
689
690        int pushResult = ResponseCodes.OBEX_HTTP_OK;
691        try {
692            op.sendHeaders(reply);
693            outputStream = op.openOutputStream();
694            outputStream.flush();
695        } catch (IOException e) {
696            Log.e(TAG, e.toString());
697            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
698        } finally {
699            if (!closeStream(outputStream, op)) {
700                pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
701            }
702        }
703        return pushResult;
704    }
705
706    /** Function to send vcard data to client */
707    private final int pushBytes(Operation op, final String vcardString) {
708        if (vcardString == null) {
709            Log.w(TAG, "vcardString is null!");
710            return ResponseCodes.OBEX_HTTP_OK;
711        }
712
713        OutputStream outputStream = null;
714        int pushResult = ResponseCodes.OBEX_HTTP_OK;
715        try {
716            outputStream = op.openOutputStream();
717            outputStream.write(vcardString.getBytes());
718            if (V) Log.v(TAG, "Send Data complete!");
719        } catch (IOException e) {
720            Log.e(TAG, "open/write outputstrem failed" + e.toString());
721            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
722        }
723
724        if (!closeStream(outputStream, op)) {
725            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
726        }
727
728        return pushResult;
729    }
730
731    private final int handleAppParaForResponse(AppParamValue appParamValue, int size,
732            HeaderSet reply, Operation op) {
733        byte[] misnum = new byte[1];
734        ApplicationParameter ap = new ApplicationParameter();
735
736        // In such case, PCE only want the number of index.
737        // So response not contain any Body header.
738        if (mNeedPhonebookSize) {
739            if (V) Log.v(TAG, "Need Phonebook size in response header.");
740            mNeedPhonebookSize = false;
741
742            byte[] pbsize = new byte[2];
743
744            pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
745            pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
746            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
747                    ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
748
749            if (mNeedNewMissedCallsNum) {
750                mNeedNewMissedCallsNum = false;
751                int nmnum = size - mMissedCallSize;
752                mMissedCallSize = size;
753
754                nmnum = nmnum > 0 ? nmnum : 0;
755                misnum[0] = (byte)nmnum;
756                ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
757                        ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
758                if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
759                            + nmnum);
760            }
761            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
762
763            if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
764
765            return pushHeader(op, reply);
766        }
767
768        // Only apply to "mch" download/listing.
769        // NewMissedCalls is used only in the response, together with Body
770        // header.
771        if (mNeedNewMissedCallsNum) {
772            if (V) Log.v(TAG, "Need new missed call num in response header.");
773            mNeedNewMissedCallsNum = false;
774
775            int nmnum = size - mMissedCallSize;
776            mMissedCallSize = size;
777
778            nmnum = nmnum > 0 ? nmnum : 0;
779            misnum[0] = (byte)nmnum;
780            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
781                    ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
782            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
783            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
784                        + nmnum);
785
786            // Only Specifies the headers, not write for now, will write to PCE
787            // together with Body
788            try {
789                op.sendHeaders(reply);
790            } catch (IOException e) {
791                Log.e(TAG, e.toString());
792                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
793            }
794        }
795        return NEED_SEND_BODY;
796    }
797
798    private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
799            HeaderSet reply, Operation op) {
800        String searchAttr = appParamValue.searchAttr.trim();
801
802        if (searchAttr == null || searchAttr.length() == 0) {
803            // If searchAttr is not set by PCE, set default value per spec.
804            appParamValue.searchAttr = "0";
805            if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
806        } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
807            Log.w(TAG, "search attr not supported");
808            if (searchAttr.equals("2")) {
809                // search by sound is not supported currently
810                Log.w(TAG, "do not support search by sound");
811                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
812            }
813            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
814        } else {
815            Log.i(TAG, "searchAttr is valid: " + searchAttr);
816        }
817
818        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
819        int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op);
820        if (needSendBody != NEED_SEND_BODY) {
821            return needSendBody;
822        }
823
824        if (size == 0) {
825            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
826            return ResponseCodes.OBEX_HTTP_OK;
827        }
828
829        String orderPara = appParamValue.order.trim();
830        if (TextUtils.isEmpty(orderPara)) {
831            // If order parameter is not set by PCE, set default value per spec.
832            orderPara = "0";
833            if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
834                       "Assume order by 'Indexed' by default");
835        } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
836            if (V) Log.v(TAG, "Order parameter is not supported: " + appParamValue.order);
837            if (orderPara.equals("2")) {
838                // Order by sound is not supported currently
839                Log.w(TAG, "Do not support order by sound");
840                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
841            }
842            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
843        } else {
844            Log.i(TAG, "Order parameter is valid: " + orderPara);
845        }
846
847        if (orderPara.equals("0")) {
848            mOrderBy = ORDER_BY_INDEXED;
849        } else if (orderPara.equals("1")) {
850            mOrderBy = ORDER_BY_ALPHABETICAL;
851        }
852
853        int sendResult = sendVcardListingXml(appParamValue.needTag, op, appParamValue.maxListCount,
854                appParamValue.listStartOffset, appParamValue.searchValue,
855                appParamValue.searchAttr);
856        return sendResult;
857    }
858
859    private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue,
860            Operation op, final String name, final String current_path) {
861        if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
862            if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
863            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
864        }
865        String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
866        int intIndex = 0;
867        if (strIndex.trim().length() != 0) {
868            try {
869                intIndex = Integer.parseInt(strIndex);
870            } catch (NumberFormatException e) {
871                Log.e(TAG, "catch number format exception " + e.toString());
872                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
873            }
874        }
875
876        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
877        if (size == 0) {
878            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
879            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
880        }
881
882        boolean vcard21 = appParamValue.vcard21;
883        if (appParamValue.needTag == 0) {
884            Log.w(TAG, "wrong path!");
885            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
886        } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
887            if (intIndex < 0 || intIndex >= size) {
888                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
889                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
890            } else if (intIndex == 0) {
891                // For PB_PATH, 0.vcf is the phone number of this phone.
892                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null);
893                return pushBytes(op, ownerVcard);
894            } else {
895                return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
896                        mOrderBy, appParamValue.ignorefilter, appParamValue.filter);
897            }
898        } else {
899            if (intIndex <= 0 || intIndex > size) {
900                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
901                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
902            }
903            // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
904            // begin from 1.vcf
905            if (intIndex >= 1) {
906                return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
907                        intIndex, intIndex, vcard21, appParamValue.ignorefilter,
908                        appParamValue.filter);
909            }
910        }
911        return ResponseCodes.OBEX_HTTP_OK;
912    }
913
914    private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
915            Operation op, final String name) {
916        // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
917        if (name != null) {
918            int dotIndex = name.indexOf(".");
919            String vcf = "vcf";
920            if (dotIndex >= 0 && dotIndex <= name.length()) {
921                if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
922                    Log.w(TAG, "name is not .vcf");
923                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
924                }
925            }
926        } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
927
928        int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
929        int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op);
930        if (needSendBody != NEED_SEND_BODY) {
931            return needSendBody;
932        }
933
934        if (pbSize == 0) {
935            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
936            return ResponseCodes.OBEX_HTTP_OK;
937        }
938
939        int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
940                : pbSize;
941        int startPoint = appParamValue.listStartOffset;
942        if (startPoint < 0 || startPoint >= pbSize) {
943            Log.w(TAG, "listStartOffset is not correct! " + startPoint);
944            return ResponseCodes.OBEX_HTTP_OK;
945        }
946
947        // Limit the number of call log to CALLLOG_NUM_LIMIT
948        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
949            if (requestSize > CALLLOG_NUM_LIMIT) {
950               requestSize = CALLLOG_NUM_LIMIT;
951            }
952        }
953
954        int endPoint = startPoint + requestSize - 1;
955        if (endPoint > pbSize - 1) {
956            endPoint = pbSize - 1;
957        }
958        if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
959                startPoint + " endPoint=" + endPoint);
960
961        boolean vcard21 = appParamValue.vcard21;
962        if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
963            if (startPoint == 0) {
964                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null);
965                if (endPoint == 0) {
966                    return pushBytes(op, ownerVcard);
967                } else {
968                    return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
969                            ownerVcard, appParamValue.ignorefilter, appParamValue.filter);
970                }
971            } else {
972                return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
973                        vcard21, null, appParamValue.ignorefilter, appParamValue.filter);
974            }
975        } else {
976            return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
977                    startPoint + 1, endPoint + 1, vcard21, appParamValue.ignorefilter,
978                    appParamValue.filter);
979        }
980    }
981
982    public static boolean closeStream(final OutputStream out, final Operation op) {
983        boolean returnvalue = true;
984        try {
985            if (out != null) {
986                out.close();
987            }
988        } catch (IOException e) {
989            Log.e(TAG, "outputStream close failed" + e.toString());
990            returnvalue = false;
991        }
992        try {
993            if (op != null) {
994                op.close();
995            }
996        } catch (IOException e) {
997            Log.e(TAG, "operation close failed" + e.toString());
998            returnvalue = false;
999        }
1000        return returnvalue;
1001    }
1002
1003    // Reserved for future use. In case PSE challenge PCE and PCE input wrong
1004    // session key.
1005    public final void onAuthenticationFailure(final byte[] userName) {
1006    }
1007
1008    public static final String createSelectionPara(final int type) {
1009        String selection = null;
1010        switch (type) {
1011            case ContentType.INCOMING_CALL_HISTORY:
1012                selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
1013                break;
1014            case ContentType.OUTGOING_CALL_HISTORY:
1015                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
1016                break;
1017            case ContentType.MISSED_CALL_HISTORY:
1018                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
1019                break;
1020            default:
1021                break;
1022        }
1023        if (V) Log.v(TAG, "Call log selection: " + selection);
1024        return selection;
1025    }
1026
1027    /**
1028     * XML encode special characters in the name field
1029     */
1030    private void xmlEncode(String name, StringBuilder result) {
1031        if (name == null) {
1032            return;
1033        }
1034
1035        final StringCharacterIterator iterator = new StringCharacterIterator(name);
1036        char character =  iterator.current();
1037        while (character != CharacterIterator.DONE ){
1038            if (character == '<') {
1039                result.append("&lt;");
1040            }
1041            else if (character == '>') {
1042                result.append("&gt;");
1043            }
1044            else if (character == '\"') {
1045                result.append("&quot;");
1046            }
1047            else if (character == '\'') {
1048                result.append("&#039;");
1049            }
1050            else if (character == '&') {
1051                result.append("&amp;");
1052            }
1053            else {
1054                // The char is not a special one, add it to the result as is
1055                result.append(character);
1056            }
1057            character = iterator.next();
1058        }
1059    }
1060
1061    private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
1062        result.append("<card handle=\"");
1063        result.append(vcfIndex);
1064        result.append(".vcf\" name=\"");
1065        xmlEncode(name, result);
1066        result.append("\"/>");
1067    }
1068
1069    private void notifyUpdateWakeLock() {
1070        Message msg = Message.obtain(mCallback);
1071        msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK;
1072        msg.sendToTarget();
1073    }
1074
1075    public static final void logHeader(HeaderSet hs) {
1076        Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1077        try {
1078
1079            Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1080            Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1081            Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1082            Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1083            Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1084            Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1085            Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1086            Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1087            Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1088            Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1089            Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1090            Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1091        } catch (IOException e) {
1092            Log.e(TAG, "dump HeaderSet error " + e);
1093        }
1094    }
1095}
1096