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