BluetoothPbapObexServer.java revision 9522370104410f24602ac98172bfbda27f89780d
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        int vcardStringLen = vcardString.length();
674        if (D) Log.d(TAG, "Send Data: len=" + vcardStringLen);
675
676        OutputStream outputStream = null;
677        int pushResult = ResponseCodes.OBEX_HTTP_OK;
678        try {
679            outputStream = op.openOutputStream();
680        } catch (IOException e) {
681            Log.e(TAG, "open outputstrem failed" + e.toString());
682            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
683        }
684
685        int position = 0;
686        long timestamp = 0;
687        int outputBufferSize = op.getMaxPacketSize();
688        if (V) Log.v(TAG, "outputBufferSize = " + outputBufferSize);
689        while (position != vcardStringLen) {
690            if (sIsAborted) {
691                ((ServerOperation)op).isAborted = true;
692                sIsAborted = false;
693                break;
694            }
695            if (V) timestamp = System.currentTimeMillis();
696            int readLength = outputBufferSize;
697            if (vcardStringLen - position < outputBufferSize) {
698                readLength = vcardStringLen - position;
699            }
700            String subStr = vcardString.substring(position, position + readLength);
701            try {
702                outputStream.write(subStr.getBytes(), 0, readLength);
703            } catch (IOException e) {
704                Log.e(TAG, "write outputstrem failed" + e.toString());
705                pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
706                break;
707            }
708            if (V) {
709                Log.v(TAG, "Sending vcard String position = " + position + " readLength "
710                        + readLength + " bytes took " + (System.currentTimeMillis() - timestamp)
711                        + " ms");
712            }
713            position += readLength;
714        }
715
716        if (V) Log.v(TAG, "Send Data complete!");
717
718        if (!closeStream(outputStream, op)) {
719            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
720        }
721
722        return pushResult;
723    }
724
725    private final int handleAppParaForResponse(AppParamValue appParamValue, int size,
726            HeaderSet reply, Operation op) {
727        byte[] misnum = new byte[1];
728        ApplicationParameter ap = new ApplicationParameter();
729
730        // In such case, PCE only want the number of index.
731        // So response not contain any Body header.
732        if (mNeedPhonebookSize) {
733            if (V) Log.v(TAG, "Need Phonebook size in response header.");
734            mNeedPhonebookSize = false;
735
736            byte[] pbsize = new byte[2];
737
738            pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
739            pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
740            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
741                    ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
742
743            if (mNeedNewMissedCallsNum) {
744                mNeedNewMissedCallsNum = false;
745                int nmnum = size - mMissedCallSize;
746                mMissedCallSize = size;
747
748                nmnum = nmnum > 0 ? nmnum : 0;
749                misnum[0] = (byte)nmnum;
750                ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
751                        ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
752                if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
753                            + nmnum);
754            }
755            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
756
757            if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
758
759            return pushHeader(op, reply);
760        }
761
762        // Only apply to "mch" download/listing.
763        // NewMissedCalls is used only in the response, together with Body
764        // header.
765        if (mNeedNewMissedCallsNum) {
766            if (V) Log.v(TAG, "Need new missed call num in response header.");
767            mNeedNewMissedCallsNum = false;
768
769            int nmnum = size - mMissedCallSize;
770            mMissedCallSize = size;
771
772            nmnum = nmnum > 0 ? nmnum : 0;
773            misnum[0] = (byte)nmnum;
774            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
775                    ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
776            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
777            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
778                        + nmnum);
779
780            // Only Specifies the headers, not write for now, will write to PCE
781            // together with Body
782            try {
783                op.sendHeaders(reply);
784            } catch (IOException e) {
785                Log.e(TAG, e.toString());
786                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
787            }
788        }
789        return NEED_SEND_BODY;
790    }
791
792    private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
793            HeaderSet reply, Operation op) {
794        String searchAttr = appParamValue.searchAttr.trim();
795
796        if (searchAttr == null || searchAttr.length() == 0) {
797            // If searchAttr is not set by PCE, set default value per spec.
798            appParamValue.searchAttr = "0";
799            if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
800        } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
801            Log.w(TAG, "search attr not supported");
802            if (searchAttr.equals("2")) {
803                // search by sound is not supported currently
804                Log.w(TAG, "do not support search by sound");
805                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
806            }
807            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
808        } else {
809            Log.i(TAG, "searchAttr is valid: " + searchAttr);
810        }
811
812        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
813        int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op);
814        if (needSendBody != NEED_SEND_BODY) {
815            return needSendBody;
816        }
817
818        if (size == 0) {
819            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
820            return ResponseCodes.OBEX_HTTP_OK;
821        }
822
823        String orderPara = appParamValue.order.trim();
824        if (TextUtils.isEmpty(orderPara)) {
825            // If order parameter is not set by PCE, set default value per spec.
826            orderPara = "0";
827            if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
828                       "Assume order by 'Indexed' by default");
829        } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
830            if (V) Log.v(TAG, "Order parameter is not supported: " + appParamValue.order);
831            if (orderPara.equals("2")) {
832                // Order by sound is not supported currently
833                Log.w(TAG, "Do not support order by sound");
834                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
835            }
836            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
837        } else {
838            Log.i(TAG, "Order parameter is valid: " + orderPara);
839        }
840
841        if (orderPara.equals("0")) {
842            mOrderBy = ORDER_BY_INDEXED;
843        } else if (orderPara.equals("1")) {
844            mOrderBy = ORDER_BY_ALPHABETICAL;
845        }
846
847        int sendResult = sendVcardListingXml(appParamValue.needTag, op, appParamValue.maxListCount,
848                appParamValue.listStartOffset, appParamValue.searchValue,
849                appParamValue.searchAttr);
850        return sendResult;
851    }
852
853    private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue,
854            Operation op, final String name, final String current_path) {
855        if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
856            if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
857            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
858        }
859        String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
860        int intIndex = 0;
861        if (strIndex.trim().length() != 0) {
862            try {
863                intIndex = Integer.parseInt(strIndex);
864            } catch (NumberFormatException e) {
865                Log.e(TAG, "catch number format exception " + e.toString());
866                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
867            }
868        }
869
870        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
871        if (size == 0) {
872            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
873            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
874        }
875
876        boolean vcard21 = appParamValue.vcard21;
877        if (appParamValue.needTag == 0) {
878            Log.w(TAG, "wrong path!");
879            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
880        } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
881            if (intIndex < 0 || intIndex >= size) {
882                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
883                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
884            } else if (intIndex == 0) {
885                // For PB_PATH, 0.vcf is the phone number of this phone.
886                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21);
887                return pushBytes(op, ownerVcard);
888            } else {
889                return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
890                        mOrderBy );
891            }
892        } else {
893            if (intIndex <= 0 || intIndex > size) {
894                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
895                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
896            }
897            // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
898            // begin from 1.vcf
899            if (intIndex >= 1) {
900                return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
901                        intIndex, intIndex, vcard21);
902            }
903        }
904        return ResponseCodes.OBEX_HTTP_OK;
905    }
906
907    private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
908            Operation op, final String name) {
909        // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
910        if (name != null) {
911            int dotIndex = name.indexOf(".");
912            String vcf = "vcf";
913            if (dotIndex >= 0 && dotIndex <= name.length()) {
914                if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
915                    Log.w(TAG, "name is not .vcf");
916                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
917                }
918            }
919        } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
920
921        int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
922        int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op);
923        if (needSendBody != NEED_SEND_BODY) {
924            return needSendBody;
925        }
926
927        if (pbSize == 0) {
928            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
929            return ResponseCodes.OBEX_HTTP_OK;
930        }
931
932        int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
933                : pbSize;
934        int startPoint = appParamValue.listStartOffset;
935        if (startPoint < 0 || startPoint >= pbSize) {
936            Log.w(TAG, "listStartOffset is not correct! " + startPoint);
937            return ResponseCodes.OBEX_HTTP_OK;
938        }
939
940        // Limit the number of call log to CALLLOG_NUM_LIMIT
941        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
942            if (requestSize > CALLLOG_NUM_LIMIT) {
943               requestSize = CALLLOG_NUM_LIMIT;
944            }
945        }
946
947        int endPoint = startPoint + requestSize - 1;
948        if (endPoint > pbSize - 1) {
949            endPoint = pbSize - 1;
950        }
951        if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
952                startPoint + " endPoint=" + endPoint);
953
954        boolean vcard21 = appParamValue.vcard21;
955        if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
956            if (startPoint == 0) {
957                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21);
958                if (endPoint == 0) {
959                    return pushBytes(op, ownerVcard);
960                } else {
961                    return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
962                            ownerVcard);
963                }
964            } else {
965                return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
966                        vcard21, null);
967            }
968        } else {
969            return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
970                    startPoint + 1, endPoint + 1, vcard21);
971        }
972    }
973
974    public static boolean closeStream(final OutputStream out, final Operation op) {
975        boolean returnvalue = true;
976        try {
977            if (out != null) {
978                out.close();
979            }
980        } catch (IOException e) {
981            Log.e(TAG, "outputStream close failed" + e.toString());
982            returnvalue = false;
983        }
984        try {
985            if (op != null) {
986                op.close();
987            }
988        } catch (IOException e) {
989            Log.e(TAG, "operation close failed" + e.toString());
990            returnvalue = false;
991        }
992        return returnvalue;
993    }
994
995    // Reserved for future use. In case PSE challenge PCE and PCE input wrong
996    // session key.
997    public final void onAuthenticationFailure(final byte[] userName) {
998    }
999
1000    public static final String createSelectionPara(final int type) {
1001        String selection = null;
1002        switch (type) {
1003            case ContentType.INCOMING_CALL_HISTORY:
1004                selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
1005                break;
1006            case ContentType.OUTGOING_CALL_HISTORY:
1007                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
1008                break;
1009            case ContentType.MISSED_CALL_HISTORY:
1010                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
1011                break;
1012            default:
1013                break;
1014        }
1015        if (V) Log.v(TAG, "Call log selection: " + selection);
1016        return selection;
1017    }
1018
1019    public static final void logHeader(HeaderSet hs) {
1020        Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1021        try {
1022
1023            Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1024            Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1025            Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1026            Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1027            Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1028            Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1029            Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1030            Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1031            Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1032            Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1033            Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1034            Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1035        } catch (IOException e) {
1036            Log.e(TAG, "dump HeaderSet error " + e);
1037        }
1038    }
1039}
1040