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        // set initial value when ObexServer created
177        mMissedCallSize = mVcardManager.getPhonebookSize(ContentType.MISSED_CALL_HISTORY);
178        if (D) Log.d(TAG, "Initialize mMissedCallSize=" + mMissedCallSize);
179    }
180
181    @Override
182    public int onConnect(final HeaderSet request, HeaderSet reply) {
183        if (V) logHeader(request);
184        try {
185            byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
186            if (uuid == null) {
187                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
188            }
189            if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
190
191            if (uuid.length != UUID_LENGTH) {
192                Log.w(TAG, "Wrong UUID length");
193                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
194            }
195            for (int i = 0; i < UUID_LENGTH; i++) {
196                if (uuid[i] != PBAP_TARGET[i]) {
197                    Log.w(TAG, "Wrong UUID");
198                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
199                }
200            }
201            reply.setHeader(HeaderSet.WHO, uuid);
202        } catch (IOException e) {
203            Log.e(TAG, e.toString());
204            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
205        }
206
207        try {
208            byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
209            if (remote != null) {
210                if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
211                reply.setHeader(HeaderSet.TARGET, remote);
212            }
213        } catch (IOException e) {
214            Log.e(TAG, e.toString());
215            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
216        }
217
218        if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
219                "MSG_SESSION_ESTABLISHED msg.");
220
221        Message msg = Message.obtain(mCallback);
222        msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
223        msg.sendToTarget();
224
225        return ResponseCodes.OBEX_HTTP_OK;
226    }
227
228    @Override
229    public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
230        if (D) Log.d(TAG, "onDisconnect(): enter");
231        if (V) logHeader(req);
232
233        resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
234        if (mCallback != null) {
235            Message msg = Message.obtain(mCallback);
236            msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED;
237            msg.sendToTarget();
238            if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
239        }
240    }
241
242    @Override
243    public int onAbort(HeaderSet request, HeaderSet reply) {
244        if (D) Log.d(TAG, "onAbort(): enter.");
245        sIsAborted = true;
246        return ResponseCodes.OBEX_HTTP_OK;
247    }
248
249    @Override
250    public int onPut(final Operation op) {
251        if (D) Log.d(TAG, "onPut(): not support PUT request.");
252        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
253    }
254
255    @Override
256    public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
257            final boolean create) {
258        if (V) logHeader(request);
259        if (D) Log.d(TAG, "before setPath, mCurrentPath ==  " + mCurrentPath);
260
261        String current_path_tmp = mCurrentPath;
262        String tmp_path = null;
263        try {
264            tmp_path = (String)request.getHeader(HeaderSet.NAME);
265        } catch (IOException e) {
266            Log.e(TAG, "Get name header fail");
267            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
268        }
269        if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmp_path);
270
271        if (backup) {
272            if (current_path_tmp.length() != 0) {
273                current_path_tmp = current_path_tmp.substring(0,
274                        current_path_tmp.lastIndexOf("/"));
275            }
276        } else {
277            if (tmp_path == null) {
278                current_path_tmp = "";
279            } else {
280                current_path_tmp = current_path_tmp + "/" + tmp_path;
281            }
282        }
283
284        if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) {
285            if (create) {
286                Log.w(TAG, "path create is forbidden!");
287                return ResponseCodes.OBEX_HTTP_FORBIDDEN;
288            } else {
289                Log.w(TAG, "path is not legal");
290                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
291            }
292        }
293        mCurrentPath = current_path_tmp;
294        if (V) Log.v(TAG, "after setPath, mCurrentPath ==  " + mCurrentPath);
295
296        return ResponseCodes.OBEX_HTTP_OK;
297    }
298
299    @Override
300    public void onClose() {
301        if (mCallback != null) {
302            Message msg = Message.obtain(mCallback);
303            msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
304            msg.sendToTarget();
305            if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
306        }
307    }
308
309    @Override
310    public int onGet(Operation op) {
311        sIsAborted = false;
312        HeaderSet request = null;
313        HeaderSet reply = new HeaderSet();
314        String type = "";
315        String name = "";
316        byte[] appParam = null;
317        AppParamValue appParamValue = new AppParamValue();
318        try {
319            request = op.getReceivedHeader();
320            type = (String)request.getHeader(HeaderSet.TYPE);
321            name = (String)request.getHeader(HeaderSet.NAME);
322            appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
323        } catch (IOException e) {
324            Log.e(TAG, "request headers error");
325            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
326        }
327
328        if (V) logHeader(request);
329        if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name);
330
331        if (type == null) {
332            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
333        }
334        // Accroding to specification,the name header could be omitted such as
335        // sony erriccsonHBH-DS980
336
337        // For "x-bt/phonebook" and "x-bt/vcard-listing":
338        // if name == null, guess what carkit actually want from current path
339        // For "x-bt/vcard":
340        // We decide which kind of content client would like per current path
341
342        boolean validName = true;
343        if (TextUtils.isEmpty(name)) {
344            validName = false;
345        }
346
347        if (!validName || (validName && type.equals(TYPE_VCARD))) {
348            if (D) Log.d(TAG, "Guess what carkit actually want from current path (" +
349                    mCurrentPath + ")");
350
351            if (mCurrentPath.equals(PB_PATH)) {
352                appParamValue.needTag = ContentType.PHONEBOOK;
353            } else if (mCurrentPath.equals(ICH_PATH)) {
354                appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
355            } else if (mCurrentPath.equals(OCH_PATH)) {
356                appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
357            } else if (mCurrentPath.equals(MCH_PATH)) {
358                appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
359                mNeedNewMissedCallsNum = true;
360            } else if (mCurrentPath.equals(CCH_PATH)) {
361                appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
362            } else {
363                Log.w(TAG, "mCurrentpath is not valid path!!!");
364                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
365            }
366            if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
367        } else {
368            // Not support SIM card currently
369            if (name.contains(SIM1.subSequence(0, SIM1.length()))) {
370                Log.w(TAG, "Not support access SIM card info!");
371                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
372            }
373
374            // we have weak name checking here to provide better
375            // compatibility with other devices,although unique name such as
376            // "pb.vcf" is required by SIG spec.
377            if (name.contains(PB.subSequence(0, PB.length()))) {
378                appParamValue.needTag = ContentType.PHONEBOOK;
379                if (D) Log.v(TAG, "download phonebook request");
380            } else if (name.contains(ICH.subSequence(0, ICH.length()))) {
381                appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
382                if (D) Log.v(TAG, "download incoming calls request");
383            } else if (name.contains(OCH.subSequence(0, OCH.length()))) {
384                appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
385                if (D) Log.v(TAG, "download outgoing calls request");
386            } else if (name.contains(MCH.subSequence(0, MCH.length()))) {
387                appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
388                mNeedNewMissedCallsNum = true;
389                if (D) Log.v(TAG, "download missed calls request");
390            } else if (name.contains(CCH.subSequence(0, CCH.length()))) {
391                appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
392                if (D) Log.v(TAG, "download combined calls request");
393            } else {
394                Log.w(TAG, "Input name doesn't contain valid info!!!");
395                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
396            }
397        }
398
399        if ((appParam != null) && !parseApplicationParameter(appParam, appParamValue)) {
400            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
401        }
402
403        // listing request
404        if (type.equals(TYPE_LISTING)) {
405            return pullVcardListing(appParam, appParamValue, reply, op);
406        }
407        // pull vcard entry request
408        else if (type.equals(TYPE_VCARD)) {
409            return pullVcardEntry(appParam, appParamValue, op, name, mCurrentPath);
410        }
411        // down load phone book request
412        else if (type.equals(TYPE_PB)) {
413            return pullPhonebook(appParam, appParamValue, reply, op, name);
414        } else {
415            Log.w(TAG, "unknown type request!!!");
416            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
417        }
418    }
419
420    /** check whether path is legal */
421    private final boolean isLegalPath(final String str) {
422        if (str.length() == 0) {
423            return true;
424        }
425        for (int i = 0; i < LEGAL_PATH.length; i++) {
426            if (str.equals(LEGAL_PATH[i])) {
427                return true;
428            }
429        }
430        return false;
431    }
432
433    private class AppParamValue {
434        public int maxListCount;
435
436        public int listStartOffset;
437
438        public String searchValue;
439
440        // Indicate which vCard parameter the search operation shall be carried
441        // out on. Can be "Name | Number | Sound", default value is "Name".
442        public String searchAttr;
443
444        // Indicate which sorting order shall be used for the
445        // <x-bt/vcard-listing> listing object.
446        // Can be "Alphabetical | Indexed | Phonetical", default value is
447        // "Indexed".
448        public String order;
449
450        public int needTag;
451
452        public boolean vcard21;
453
454        public AppParamValue() {
455            maxListCount = 0xFFFF;
456            listStartOffset = 0;
457            searchValue = "";
458            searchAttr = "";
459            order = "";
460            needTag = 0x00;
461            vcard21 = true;
462        }
463
464        public void dump() {
465            Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
466                    + " searchValue=" + searchValue + " searchAttr=" + searchAttr + " needTag="
467                    + needTag + " vcard21=" + vcard21 + " order=" + order);
468        }
469    }
470
471    /** To parse obex application parameter */
472    private final boolean parseApplicationParameter(final byte[] appParam,
473            AppParamValue appParamValue) {
474        int i = 0;
475        boolean parseOk = true;
476        while (i < appParam.length) {
477            switch (appParam[i]) {
478                case ApplicationParameter.TRIPLET_TAGID.FILTER_TAGID:
479                    i += 2; // length and tag field in triplet
480                    i += ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH;
481                    break;
482                case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID:
483                    i += 2; // length and tag field in triplet
484                    appParamValue.order = Byte.toString(appParam[i]);
485                    i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH;
486                    break;
487                case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID:
488                    i += 1; // length field in triplet
489                    // length of search value is variable
490                    int length = appParam[i];
491                    if (length == 0) {
492                        parseOk = false;
493                        break;
494                    }
495                    if (appParam[i+length] == 0x0) {
496                        appParamValue.searchValue = new String(appParam, i + 1, length-1);
497                    } else {
498                        appParamValue.searchValue = new String(appParam, i + 1, length);
499                    }
500                    i += length;
501                    i += 1;
502                    break;
503                case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
504                    i += 2;
505                    appParamValue.searchAttr = Byte.toString(appParam[i]);
506                    i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
507                    break;
508                case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
509                    i += 2;
510                    if (appParam[i] == 0 && appParam[i + 1] == 0) {
511                        mNeedPhonebookSize = true;
512                    } else {
513                        int highValue = appParam[i] & 0xff;
514                        int lowValue = appParam[i + 1] & 0xff;
515                        appParamValue.maxListCount = highValue * 256 + lowValue;
516                    }
517                    i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
518                    break;
519                case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
520                    i += 2;
521                    int highValue = appParam[i] & 0xff;
522                    int lowValue = appParam[i + 1] & 0xff;
523                    appParamValue.listStartOffset = highValue * 256 + lowValue;
524                    i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
525                    break;
526                case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
527                    i += 2;// length field in triplet
528                    if (appParam[i] != 0) {
529                        appParamValue.vcard21 = false;
530                    }
531                    i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
532                    break;
533                default:
534                    parseOk = false;
535                    Log.e(TAG, "Parse Application Parameter error");
536                    break;
537            }
538        }
539
540        if (D) appParamValue.dump();
541
542        return parseOk;
543    }
544
545    /** Form and Send an XML format String to client for Phone book listing */
546    private final int sendVcardListingXml(final int type, Operation op,
547            final int maxListCount, final int listStartOffset, final String searchValue,
548            String searchAttr) {
549        StringBuilder result = new StringBuilder();
550        int itemsFound = 0;
551        result.append("<?xml version=\"1.0\"?>");
552        result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
553        result.append("<vCard-listing version=\"1.0\">");
554
555        // Phonebook listing request
556        if (type == ContentType.PHONEBOOK) {
557            if (searchAttr.equals("0")) { // search by name
558                itemsFound = createList(maxListCount, listStartOffset, searchValue, result,
559                        "name");
560            } else if (searchAttr.equals("1")) { // search by number
561                itemsFound = createList(maxListCount, listStartOffset, searchValue, result,
562                        "number");
563            }// end of search by number
564            else {
565                return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
566            }
567        }
568        // Call history listing request
569        else {
570            ArrayList<String> nameList = mVcardManager.loadCallHistoryList(type);
571            int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size();
572            int startPoint = listStartOffset;
573            int endPoint = startPoint + requestSize;
574            if (endPoint > nameList.size()) {
575                endPoint = nameList.size();
576            }
577            if (D) Log.d(TAG, "call log list, size=" + requestSize + " offset=" + listStartOffset);
578
579            for (int j = startPoint; j < endPoint; j++) {
580                writeVCardEntry(j+1, nameList.get(j),result);
581            }
582        }
583        result.append("</vCard-listing>");
584
585        if (V) Log.v(TAG, "itemsFound =" + itemsFound);
586
587        return pushBytes(op, result.toString());
588    }
589
590    private int createList(final int maxListCount, final int listStartOffset,
591            final String searchValue, StringBuilder result, String type) {
592        int itemsFound = 0;
593        ArrayList<String> nameList = mVcardManager.getPhonebookNameList(mOrderBy);
594        final int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size();
595        final int listSize = nameList.size();
596        String compareValue = "", currentValue;
597
598        if (D) Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset="
599                    + listStartOffset + " searchValue=" + searchValue);
600
601        if (type.equals("number")) {
602            // query the number, to get the names
603            ArrayList<String> names = mVcardManager.getContactNamesByNumber(searchValue);
604            for (int i = 0; i < names.size(); i++) {
605                compareValue = names.get(i).trim();
606                if (D) Log.d(TAG, "compareValue=" + compareValue);
607                for (int pos = listStartOffset; pos < listSize &&
608                        itemsFound < requestSize; pos++) {
609                    currentValue = nameList.get(pos);
610                    if (D) Log.d(TAG, "currentValue=" + currentValue);
611                    if (currentValue.startsWith(compareValue)) {
612                        itemsFound++;
613                        writeVCardEntry(pos, currentValue,result);
614                    }
615                }
616                if (itemsFound >= requestSize) {
617                    break;
618                }
619            }
620        } else {
621            if (searchValue != null) {
622                compareValue = searchValue.trim();
623            }
624            for (int pos = listStartOffset; pos < listSize &&
625                    itemsFound < requestSize; pos++) {
626                currentValue = nameList.get(pos);
627                if (D) Log.d(TAG, "currentValue=" + currentValue);
628                if (searchValue == null || currentValue.startsWith(compareValue)) {
629                    itemsFound++;
630                    writeVCardEntry(pos, currentValue,result);
631                }
632            }
633        }
634        return itemsFound;
635    }
636
637    /**
638     * Function to send obex header back to client such as get phonebook size
639     * request
640     */
641    private final int pushHeader(final Operation op, final HeaderSet reply) {
642        OutputStream outputStream = null;
643
644        if (D) Log.d(TAG, "Push Header");
645        if (D) Log.d(TAG, reply.toString());
646
647        int pushResult = ResponseCodes.OBEX_HTTP_OK;
648        try {
649            op.sendHeaders(reply);
650            outputStream = op.openOutputStream();
651            outputStream.flush();
652        } catch (IOException e) {
653            Log.e(TAG, e.toString());
654            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
655        } finally {
656            if (!closeStream(outputStream, op)) {
657                pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
658            }
659        }
660        return pushResult;
661    }
662
663    /** Function to send vcard data to client */
664    private final int pushBytes(Operation op, final String vcardString) {
665        if (vcardString == null) {
666            Log.w(TAG, "vcardString is null!");
667            return ResponseCodes.OBEX_HTTP_OK;
668        }
669
670        OutputStream outputStream = null;
671        int pushResult = ResponseCodes.OBEX_HTTP_OK;
672        try {
673            outputStream = op.openOutputStream();
674            outputStream.write(vcardString.getBytes());
675            if (V) Log.v(TAG, "Send Data complete!");
676        } catch (IOException e) {
677            Log.e(TAG, "open/write outputstrem failed" + e.toString());
678            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
679        }
680
681        if (!closeStream(outputStream, op)) {
682            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
683        }
684
685        return pushResult;
686    }
687
688    private final int handleAppParaForResponse(AppParamValue appParamValue, int size,
689            HeaderSet reply, Operation op) {
690        byte[] misnum = new byte[1];
691        ApplicationParameter ap = new ApplicationParameter();
692
693        // In such case, PCE only want the number of index.
694        // So response not contain any Body header.
695        if (mNeedPhonebookSize) {
696            if (V) Log.v(TAG, "Need Phonebook size in response header.");
697            mNeedPhonebookSize = false;
698
699            byte[] pbsize = new byte[2];
700
701            pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
702            pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
703            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
704                    ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
705
706            if (mNeedNewMissedCallsNum) {
707                mNeedNewMissedCallsNum = false;
708                int nmnum = size - mMissedCallSize;
709                mMissedCallSize = size;
710
711                nmnum = nmnum > 0 ? nmnum : 0;
712                misnum[0] = (byte)nmnum;
713                ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
714                        ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
715                if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
716                            + nmnum);
717            }
718            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
719
720            if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
721
722            return pushHeader(op, reply);
723        }
724
725        // Only apply to "mch" download/listing.
726        // NewMissedCalls is used only in the response, together with Body
727        // header.
728        if (mNeedNewMissedCallsNum) {
729            if (V) Log.v(TAG, "Need new missed call num in response header.");
730            mNeedNewMissedCallsNum = false;
731
732            int nmnum = size - mMissedCallSize;
733            mMissedCallSize = size;
734
735            nmnum = nmnum > 0 ? nmnum : 0;
736            misnum[0] = (byte)nmnum;
737            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
738                    ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
739            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
740            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
741                        + nmnum);
742
743            // Only Specifies the headers, not write for now, will write to PCE
744            // together with Body
745            try {
746                op.sendHeaders(reply);
747            } catch (IOException e) {
748                Log.e(TAG, e.toString());
749                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
750            }
751        }
752        return NEED_SEND_BODY;
753    }
754
755    private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
756            HeaderSet reply, Operation op) {
757        String searchAttr = appParamValue.searchAttr.trim();
758
759        if (searchAttr == null || searchAttr.length() == 0) {
760            // If searchAttr is not set by PCE, set default value per spec.
761            appParamValue.searchAttr = "0";
762            if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
763        } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
764            Log.w(TAG, "search attr not supported");
765            if (searchAttr.equals("2")) {
766                // search by sound is not supported currently
767                Log.w(TAG, "do not support search by sound");
768                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
769            }
770            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
771        } else {
772            Log.i(TAG, "searchAttr is valid: " + searchAttr);
773        }
774
775        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
776        int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op);
777        if (needSendBody != NEED_SEND_BODY) {
778            return needSendBody;
779        }
780
781        if (size == 0) {
782            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
783            return ResponseCodes.OBEX_HTTP_OK;
784        }
785
786        String orderPara = appParamValue.order.trim();
787        if (TextUtils.isEmpty(orderPara)) {
788            // If order parameter is not set by PCE, set default value per spec.
789            orderPara = "0";
790            if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
791                       "Assume order by 'Indexed' by default");
792        } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
793            if (V) Log.v(TAG, "Order parameter is not supported: " + appParamValue.order);
794            if (orderPara.equals("2")) {
795                // Order by sound is not supported currently
796                Log.w(TAG, "Do not support order by sound");
797                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
798            }
799            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
800        } else {
801            Log.i(TAG, "Order parameter is valid: " + orderPara);
802        }
803
804        if (orderPara.equals("0")) {
805            mOrderBy = ORDER_BY_INDEXED;
806        } else if (orderPara.equals("1")) {
807            mOrderBy = ORDER_BY_ALPHABETICAL;
808        }
809
810        int sendResult = sendVcardListingXml(appParamValue.needTag, op, appParamValue.maxListCount,
811                appParamValue.listStartOffset, appParamValue.searchValue,
812                appParamValue.searchAttr);
813        return sendResult;
814    }
815
816    private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue,
817            Operation op, final String name, final String current_path) {
818        if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
819            if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
820            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
821        }
822        String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
823        int intIndex = 0;
824        if (strIndex.trim().length() != 0) {
825            try {
826                intIndex = Integer.parseInt(strIndex);
827            } catch (NumberFormatException e) {
828                Log.e(TAG, "catch number format exception " + e.toString());
829                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
830            }
831        }
832
833        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
834        if (size == 0) {
835            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
836            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
837        }
838
839        boolean vcard21 = appParamValue.vcard21;
840        if (appParamValue.needTag == 0) {
841            Log.w(TAG, "wrong path!");
842            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
843        } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
844            if (intIndex < 0 || intIndex >= size) {
845                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
846                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
847            } else if (intIndex == 0) {
848                // For PB_PATH, 0.vcf is the phone number of this phone.
849                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null);
850                return pushBytes(op, ownerVcard);
851            } else {
852                return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
853                        mOrderBy );
854            }
855        } else {
856            if (intIndex <= 0 || intIndex > size) {
857                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
858                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
859            }
860            // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
861            // begin from 1.vcf
862            if (intIndex >= 1) {
863                return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
864                        intIndex, intIndex, vcard21);
865            }
866        }
867        return ResponseCodes.OBEX_HTTP_OK;
868    }
869
870    private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
871            Operation op, final String name) {
872        // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
873        if (name != null) {
874            int dotIndex = name.indexOf(".");
875            String vcf = "vcf";
876            if (dotIndex >= 0 && dotIndex <= name.length()) {
877                if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
878                    Log.w(TAG, "name is not .vcf");
879                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
880                }
881            }
882        } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
883
884        int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
885        int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op);
886        if (needSendBody != NEED_SEND_BODY) {
887            return needSendBody;
888        }
889
890        if (pbSize == 0) {
891            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
892            return ResponseCodes.OBEX_HTTP_OK;
893        }
894
895        int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
896                : pbSize;
897        int startPoint = appParamValue.listStartOffset;
898        if (startPoint < 0 || startPoint >= pbSize) {
899            Log.w(TAG, "listStartOffset is not correct! " + startPoint);
900            return ResponseCodes.OBEX_HTTP_OK;
901        }
902
903        // Limit the number of call log to CALLLOG_NUM_LIMIT
904        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
905            if (requestSize > CALLLOG_NUM_LIMIT) {
906               requestSize = CALLLOG_NUM_LIMIT;
907            }
908        }
909
910        int endPoint = startPoint + requestSize - 1;
911        if (endPoint > pbSize - 1) {
912            endPoint = pbSize - 1;
913        }
914        if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
915                startPoint + " endPoint=" + endPoint);
916
917        boolean vcard21 = appParamValue.vcard21;
918        if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
919            if (startPoint == 0) {
920                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null);
921                if (endPoint == 0) {
922                    return pushBytes(op, ownerVcard);
923                } else {
924                    return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
925                            ownerVcard);
926                }
927            } else {
928                return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
929                        vcard21, null);
930            }
931        } else {
932            return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
933                    startPoint + 1, endPoint + 1, vcard21);
934        }
935    }
936
937    public static boolean closeStream(final OutputStream out, final Operation op) {
938        boolean returnvalue = true;
939        try {
940            if (out != null) {
941                out.close();
942            }
943        } catch (IOException e) {
944            Log.e(TAG, "outputStream close failed" + e.toString());
945            returnvalue = false;
946        }
947        try {
948            if (op != null) {
949                op.close();
950            }
951        } catch (IOException e) {
952            Log.e(TAG, "operation close failed" + e.toString());
953            returnvalue = false;
954        }
955        return returnvalue;
956    }
957
958    // Reserved for future use. In case PSE challenge PCE and PCE input wrong
959    // session key.
960    public final void onAuthenticationFailure(final byte[] userName) {
961    }
962
963    public static final String createSelectionPara(final int type) {
964        String selection = null;
965        switch (type) {
966            case ContentType.INCOMING_CALL_HISTORY:
967                selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
968                break;
969            case ContentType.OUTGOING_CALL_HISTORY:
970                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
971                break;
972            case ContentType.MISSED_CALL_HISTORY:
973                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
974                break;
975            default:
976                break;
977        }
978        if (V) Log.v(TAG, "Call log selection: " + selection);
979        return selection;
980    }
981
982    /**
983     * XML encode special characters in the name field
984     */
985    private void xmlEncode(String name, StringBuilder result) {
986        if (name == null) {
987            return;
988        }
989
990        final StringCharacterIterator iterator = new StringCharacterIterator(name);
991        char character =  iterator.current();
992        while (character != CharacterIterator.DONE ){
993            if (character == '<') {
994                result.append("&lt;");
995            }
996            else if (character == '>') {
997                result.append("&gt;");
998            }
999            else if (character == '\"') {
1000                result.append("&quot;");
1001            }
1002            else if (character == '\'') {
1003                result.append("&#039;");
1004            }
1005            else if (character == '&') {
1006                result.append("&amp;");
1007            }
1008            else {
1009                // The char is not a special one, add it to the result as is
1010                result.append(character);
1011            }
1012            character = iterator.next();
1013        }
1014    }
1015
1016    private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
1017        result.append("<card handle=\"");
1018        result.append(vcfIndex);
1019        result.append(".vcf\" name=\"");
1020        xmlEncode(name, result);
1021        result.append("\"/>");
1022    }
1023
1024    public static final void logHeader(HeaderSet hs) {
1025        Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1026        try {
1027
1028            Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1029            Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1030            Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1031            Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1032            Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1033            Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1034            Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1035            Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1036            Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1037            Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1038            Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1039            Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1040        } catch (IOException e) {
1041            Log.e(TAG, "dump HeaderSet error " + e);
1042        }
1043    }
1044}
1045