BluetoothPbapObexServer.java revision b9cd7fee03a41e56a0cea9aa2e1af3b28c5be590
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.os.Message;
36import android.os.Handler;
37import android.util.Log;
38
39import java.io.IOException;
40import java.io.OutputStream;
41import java.io.PrintWriter;
42import java.util.ArrayList;
43
44import javax.obex.ServerRequestHandler;
45import javax.obex.ResponseCodes;
46import javax.obex.ApplicationParameter;
47import javax.obex.Operation;
48import javax.obex.HeaderSet;
49
50public class BluetoothPbapObexServer extends ServerRequestHandler {
51
52    private static final String TAG = "BluetoothPbapObexServer";
53
54    private static final int UUID_LENGTH = 16;
55
56    private static final int LEAGAL_PATH_NUM = 10;
57
58    private static final int VCARD_NAME_MIN_LEN = 5;
59
60    private long mConnectionId;
61
62    private Handler mCallback = null;
63
64    // 128 bit UUID for PBAP
65    private static final byte[] PBAP_TARGET = new byte[] {
66            0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66,
67            0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66
68    };
69
70    private static final String[] LEAGEL_PATH = {
71            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
72            "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och",
73            "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb"
74    };
75
76    // missed call history
77    private static final String MCH = "mch";
78
79    // incoming call history
80    private static final String ICH = "ich";
81
82    // outgoing call history
83    private static final String OCH = "och";
84
85    // combined call history
86    private static final String CCH = "cch";
87
88    // phone book
89    private static final String PB = "pb";
90
91    private static final String ICH_PATH = "/telecom/ich";
92
93    private static final String OCH_PATH = "/telecom/och";
94
95    private static final String MCH_PATH = "/telecom/mch";
96
97    private static final String CCH_PATH = "/telecom/cch";
98
99    private static final String PB_PATH = "/telecom/pb";
100
101    // type for list vcard objects
102    private static final String TYPE_LISTING = "x-bt/vcard-listing";
103
104    // type for get single vcard object
105    private static final String TYPE_VCARD = "x-bt/vcard";
106
107    // type for download all vcard objects
108    private static final String TYPE_PB = "x-bt/phonebook";
109
110    // record current path the client are browsing
111    private String mCurrentPath = "";
112
113    // record how many missed call happens since last client operation
114    private int mMissedCallSize = 0;
115
116    private static final int NEED_PB_SIZE = 0x01;
117
118    private static final int NEED_NEW_MISSED_CALL_NUMBER = 0x02;
119
120    public static final int NEED_PHONEBOOK = 0x04;
121
122    public static final int NEED_INCOMING_CALL_NUMBER = 0x08;
123
124    public static final int NEED_OUTGOING_CALL_NUMBER = 0x10;
125
126    public static final int NEED_MISSED_CALL_NUMBER = 0x20;
127
128    public static final int NEED_COMBINED_CALL_NUMBER = 0x40;
129
130    private static final int PRECONDITION_FAILED = -1;
131
132    private static final int INTERNAL_ERROR = -2;
133
134    public BluetoothPbapObexServer(Handler callback) {
135        super();
136        mConnectionId = -1;
137        mCallback = callback;
138    }
139
140    @Override
141    public int onConnect(final HeaderSet request, final HeaderSet reply) {
142        try {
143            byte[] uuid_tmp = (byte[])request.getHeader(HeaderSet.TARGET);
144            if (uuid_tmp.length != UUID_LENGTH) {
145                Log.w(TAG, "Wrong UUID length");
146                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
147            }
148            for (int i = 0; i < UUID_LENGTH; i++) {
149                if (uuid_tmp[i] != PBAP_TARGET[i]) {
150                    Log.w(TAG, "Wrong UUID");
151                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
152                }
153            }
154        } catch (IOException e) {
155            Log.e(TAG, e.toString());
156            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
157        }
158
159        Message msg = Message.obtain(mCallback);
160        msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
161        msg.sendToTarget();
162
163        return ResponseCodes.OBEX_HTTP_OK;
164    }
165
166    @Override
167    public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
168        resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
169        if (mCallback != null) {
170            Message msg = Message.obtain(mCallback);
171            msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED;
172            msg.sendToTarget();
173        }
174    }
175
176    @Override
177    public int onPut(final Operation op) {
178        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
179    }
180
181    @Override
182    public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
183            final boolean create) {
184        String current_path_tmp = mCurrentPath;
185        String tmp_path = null;
186        try {
187            tmp_path = (String)request.getHeader(HeaderSet.NAME);
188        } catch (IOException e) {
189            Log.e(TAG, "Get name header fail");
190            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
191        }
192        if (BluetoothPbapService.DBG) {
193            Log.d(TAG, "backup=" + backup + " create=" + create);
194        }
195        if (!backup && create) {
196            if (tmp_path == null) {
197                current_path_tmp = "";
198            } else {
199                current_path_tmp = current_path_tmp + "/" + tmp_path;
200            }
201        }
202
203        if (backup && create) {
204            if (current_path_tmp.length() != 0) {
205                current_path_tmp = current_path_tmp.substring(0, current_path_tmp.lastIndexOf("/"));
206            }
207        }
208
209        if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) {
210            Log.w(TAG, "path is not legal");
211            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
212        }
213        mCurrentPath = current_path_tmp;
214        return ResponseCodes.OBEX_HTTP_OK;
215    }
216
217    @Override
218    public void onClose() {
219        if (mCallback != null) {
220            Message msg = Message.obtain(mCallback);
221            msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
222            msg.sendToTarget();
223        }
224    }
225
226    @Override
227    public int onGet(final Operation op) {
228        HeaderSet request = null;
229        HeaderSet reply = new HeaderSet();
230        String type = "";
231        String name = "";
232        byte[] appParam = null;
233        AppParamValue appParamValue = new AppParamValue();
234        try {
235            request = op.getReceivedHeader();
236            type = (String)request.getHeader(HeaderSet.TYPE);
237            name = (String)request.getHeader(HeaderSet.NAME);
238            appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
239        } catch (IOException e) {
240            Log.e(TAG, "request headers error");
241            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
242        }
243        // Accroding to specification,the name header could be omitted such as
244        // sony erriccsonHBH-DS980
245        if (BluetoothPbapService.DBG) {
246            Log.d(TAG, "OnGet type is " + type + " name is " + name);
247        }
248        if (name == null) {
249            if (BluetoothPbapService.DBG) {
250                Log.i(TAG, "name =null,guess what carkit actually want from current path");
251            }
252            if (mCurrentPath.compareTo(PB_PATH) == 0) {
253                appParamValue.needTag |= NEED_PHONEBOOK;
254            }
255            if (mCurrentPath.compareTo(ICH_PATH) == 0) {
256                appParamValue.needTag |= NEED_INCOMING_CALL_NUMBER;
257            }
258            if (mCurrentPath.compareTo(OCH_PATH) == 0) {
259                appParamValue.needTag |= NEED_OUTGOING_CALL_NUMBER;
260            }
261            if (mCurrentPath.compareTo(CCH_PATH) == 0) {
262                appParamValue.needTag |= NEED_COMBINED_CALL_NUMBER;
263            }
264            if (mCurrentPath.compareTo(MCH_PATH) == 0) {
265                appParamValue.needTag |= NEED_MISSED_CALL_NUMBER;
266            }
267        } else {// we have weak name checking here to provide better
268            // compatibility with other devices,although unique name such as
269            // "pb.vcf" is required by SIG spec.
270            if (name.contains(PB.subSequence(0, PB.length()))) {
271                appParamValue.needTag |= NEED_PHONEBOOK;
272                if (BluetoothPbapService.DBG) {
273                    Log.v(TAG, "download phonebook request");
274                }
275            }
276            if (name.contains(ICH.subSequence(0, ICH.length()))) {
277                appParamValue.needTag |= NEED_INCOMING_CALL_NUMBER;
278                if (BluetoothPbapService.DBG) {
279                    Log.v(TAG, "download incoming calls request");
280                }
281            }
282            if (name.contains(OCH.subSequence(0, OCH.length()))) {
283                appParamValue.needTag |= NEED_OUTGOING_CALL_NUMBER;
284                if (BluetoothPbapService.DBG) {
285                    Log.v(TAG, "download outgoing calls request");
286                }
287            }
288            if (name.contains(CCH.subSequence(0, CCH.length()))) {
289                appParamValue.needTag |= NEED_COMBINED_CALL_NUMBER;
290                if (BluetoothPbapService.DBG) {
291                    Log.v(TAG, "download combined calls request");
292                }
293            }
294            if (name.contains(MCH.subSequence(0, MCH.length()))) {
295                appParamValue.needTag |= NEED_MISSED_CALL_NUMBER;
296                if (BluetoothPbapService.DBG) {
297                    Log.v(TAG, "download missed calls request");
298                }
299            }
300        }
301        // listing request
302        if (type.equals(TYPE_LISTING)) {
303            return pullVcardListing(appParam, appParamValue, reply, op);
304        }
305        // pull vcard entry request
306        else if (type.equals(TYPE_VCARD)) {
307            return pullVcardEntry(appParam, appParamValue, op, name, mCurrentPath);
308        }
309        // down load phone book request
310        else if (type.equals(TYPE_PB)) {
311            return pullPhonebook(appParam, appParamValue, reply, op, name);
312        } else {
313            Log.w(TAG, "unknown type request!!!");
314            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
315        }
316    } // end of onGet()
317
318    /** check whether path is legal */
319    private final boolean isLegalPath(final String str) {
320        if (str.length() == 0) {
321            return true;
322        }
323        for (int i = 0; i < LEAGAL_PATH_NUM; i++) {
324            if (str.equals(LEAGEL_PATH[i])) {
325                return true;
326            }
327        }
328        return false;
329    }
330
331    private class AppParamValue {
332        public int maxListCount;
333
334        public int listStartOffset;
335
336        public String searchValue;
337
338        public String searchAttr;
339
340        public int needTag;
341
342        public boolean vcard21;
343
344        public AppParamValue() {
345            maxListCount = 0;
346            listStartOffset = 0;
347            searchValue = "";
348            searchAttr = "";
349            needTag = 0x00;
350            vcard21 = true;
351        }
352
353        public void dump() {
354            Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
355                    + " searchValue=" + searchValue + " searchAttr=" + searchAttr + " needTag="
356                    + needTag + " vcard21=" + vcard21);
357        }
358    }
359
360    /** To parse obex application parameter */
361    private final boolean parseApplicationParameter(final byte[] appParam,
362            AppParamValue appParamValue) {
363        int i = 0;
364        boolean badRequest = true;
365        while (i < appParam.length) {
366            switch (appParam[i]) {
367                case ApplicationParameter.TRIPLET_TAGID.FILTER_TAGID:
368                    i += 2; // length and tag field in triplet
369                    i += ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH;
370                    break;
371                case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID:
372                    i += 2; // length and tag field in triplet
373                    i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH;
374                    break;
375                case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID:
376                    i += 1; // length field in triplet
377                    for (int k = 1; k <= appParam[i]; k++) {
378                        appParamValue.searchValue += Byte.toString(appParam[i + k]);
379                    }
380                    // length of search value is variable
381                    i += appParam[i];
382                    i += 1;
383                    break;
384                case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
385                    i += 2;
386                    appParamValue.searchAttr = Byte.toString(appParam[i]);
387                    i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
388                    break;
389                case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
390                    i += 2;
391                    if (appParam[i] == 0 && appParam[i + 1] == 0) {
392                        appParamValue.needTag |= NEED_PB_SIZE;
393                    } else {
394                        int highValue = appParam[i] & 0xff;
395                        int lowValue = appParam[i + 1] & 0xff;
396                        appParamValue.maxListCount = highValue * 256 + lowValue;
397                    }
398                    i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
399                    break;
400                case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
401                    i += 2;
402                    if (appParam[i] == 0 && appParam[i + 1] == 0) {
403                        appParamValue.listStartOffset = 0;
404                    } else {
405                        int highValue = appParam[i] & 0xff;
406                        int lowValue = appParam[i + 1] & 0xff;
407                        appParamValue.listStartOffset = highValue * 256 + lowValue;
408                    }
409                    i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
410                    break;
411                case ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID:
412                    i += 2;// length field in triplet
413                    i += ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH;
414                    appParamValue.needTag |= NEED_PB_SIZE;
415                    break;
416                case ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID:
417                    i += 1;
418                    appParamValue.needTag |= NEED_NEW_MISSED_CALL_NUMBER;
419                    i += ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH;
420                    break;
421                case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
422                    i += 2;// length field in triplet
423                    if (Byte.toString(appParam[i]).compareTo("0") != 0) {
424                        appParamValue.vcard21 = false;
425                    }
426                    i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
427                    break;
428                default:
429                    badRequest = false;
430                    break;
431            }
432        }
433        return badRequest;
434    }
435
436    /** Form and Send an XML format String to client for Phone book listing */
437    private final int createVcardListingXml(final int type, final Operation op,
438            final int maxListCount, final int listStartOffset, final String searchValue,
439            String searchAttr) {
440        OutputStream out = null;
441        StringBuilder result = new StringBuilder();
442        int itemsFound = 0;
443        result.append("<?xml version=\"1.0\"?>");
444        result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
445        result.append("<vCard-listing version=\"1.0\">");
446
447        // Phonebook listing request
448        if (type == NEED_PHONEBOOK) {
449            // if searchAttr is not set by client,make searchAttr by name
450            // as default;
451            if (searchAttr == null || searchAttr.trim().length() == 0) {
452                if (BluetoothPbapService.DBG) {
453                    Log.i(TAG, "searchAttr is not set, assume search by name");
454                }
455                searchAttr = "0";
456            }
457            // begin of search by name
458            if (searchAttr.compareTo("0") == 0) {
459                ArrayList<String> nameList = BluetoothPbapService.getPhonebookNameList();
460                int size = nameList.size() >= maxListCount ? maxListCount : nameList.size();
461                if (BluetoothPbapService.DBG) {
462                    Log.d(TAG, "search by name, size=" + size + " offset=" + listStartOffset
463                            + " searchValue=" + searchValue);
464                }
465                // if searchValue if not set by client,provide the entire
466                // list by name
467                if (searchValue == null || searchValue.trim().length() == 0) {
468                    for (int j = listStartOffset, i = 0; i < size; i++, j++) {
469                        result.append("<card handle=\"" + j + ".vcf\" name=\"" + nameList.get(j)
470                                + "\"" + "/>");
471                        itemsFound++;
472                    }
473                } else {
474                    for (int j = listStartOffset, i = 0; i < size; i++, j++) {
475                        // only find the name which begins with the searchValue
476                        if (nameList.get(j).indexOf(searchValue) == 0) {
477                            itemsFound++;
478                            result.append("<card handle=\"" + j + ".vcf\" name=\""
479                                    + nameList.get(j) + "\"" + "/>");
480                        }
481                    }
482                }
483            }// end of search by name
484            // begin of search by number
485            else if (searchAttr.compareTo("1") == 0) {
486                ArrayList<String> numberList = BluetoothPbapService.getPhonebookNumberList();
487                int size = numberList.size() >= maxListCount ? maxListCount : numberList.size();
488                if (BluetoothPbapService.DBG) {
489                    Log.d(TAG, "search by number, size=" + size + " offset=" + listStartOffset
490                            + " searchValue=" + searchValue);
491                }
492                // if searchValue if not set by client,provide the entire
493                // list by number
494                if (searchValue == null || searchValue.trim().length() == 0) {
495                    for (int j = listStartOffset, i = 0; i < size; i++, j++) {
496                        result.append("<card handle=\"" + j + ".vcf\" number=\""
497                                + numberList.get(j) + "\"" + "/>");
498                        itemsFound++;
499                    }
500                } else {
501                    for (int j = listStartOffset, i = 0; i < size; i++, j++) {
502                        // only find the name which begins with the searchValue
503                        if (numberList.get(j).indexOf(searchValue.trim()) == 0) {
504                            itemsFound++;
505                            result.append("<card handle=\"" + j + ".vcf\" number=\""
506                                    + numberList.get(j) + "\"" + "/>");
507                        }
508                    }
509                }
510            }// end of search by number
511            else {
512                return PRECONDITION_FAILED;
513            }
514        }
515        // Call history listing request
516        else {
517            ArrayList<String> nameList = BluetoothPbapService.getCallLogList(type);
518            int size = nameList.size() >= maxListCount ? maxListCount : nameList.size();
519            if (BluetoothPbapService.DBG) {
520                Log.d(TAG, "call log list, size=" + size + " offset=" + listStartOffset);
521            }
522            // listing object begin with 1.vcf
523            for (int j = listStartOffset + 1, i = 0; i < size; i++, j++) {
524                result.append("<card handle=\"" + j + ".vcf\" name=\"" + nameList.get(j - 1) + "\""
525                        + "/>");
526                itemsFound++;
527            }
528        }
529        result.append("</vCard-listing>");
530        try {
531            out = op.openOutputStream();
532            out.write(result.toString().getBytes());
533            out.flush();
534        } catch (IOException e) {
535            Log.e(TAG, "output stream fail " + e.toString());
536            itemsFound = INTERNAL_ERROR;
537        } finally {
538            if (!closeStream(out, op)) {
539                itemsFound = INTERNAL_ERROR;
540            }
541        }
542
543        if (BluetoothPbapService.DBG) {
544            Log.d(TAG, "itemsFound =" + itemsFound);
545        }
546        return itemsFound;
547    }
548
549    /**
550     * Function to send obex header back to client such as get phonebook size
551     * request
552     */
553    private final int pushHeader(final Operation op, final HeaderSet reply) {
554        OutputStream outputStream = null;
555        int ret = ResponseCodes.OBEX_HTTP_OK;
556        try {
557            op.sendHeaders(reply);
558            outputStream = op.openOutputStream();
559            outputStream.flush();
560        } catch (IOException e) {
561            Log.e(TAG, e.toString());
562            ret = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
563        } finally {
564            if (!closeStream(outputStream, op)) {
565                ret = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
566            }
567        }
568        return ret;
569    }
570
571    /** Function to send vcard data to client */
572    private final int pushBytes(final Operation op, final String string) {
573        if (string == null) {
574            return ResponseCodes.OBEX_HTTP_OK;
575        }
576        byte[] filebytes;
577        int ret = ResponseCodes.OBEX_HTTP_OK;
578        OutputStream outputStream = null;
579        if (BluetoothPbapService.DBG) {
580            Log.d(TAG, "Send Data");
581            Log.d(TAG, string);
582        }
583        try {
584            outputStream = op.openOutputStream();
585            filebytes = string.getBytes();
586            outputStream.write(filebytes);
587        } catch (IOException e) {
588            Log.e(TAG, "open outputstrem failed" + e.toString());
589            ret = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
590        } finally {
591            if (!closeStream(outputStream, op)) {
592                ret = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
593            }
594        }
595        return ret;
596    }
597
598    private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
599            HeaderSet reply, final Operation op) {
600        String searchAttr = appParamValue.searchAttr.trim();
601        if (!parseApplicationParameter(appParam, appParamValue)) {
602            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
603        }
604        if (BluetoothPbapService.DBG) {
605            appParamValue.dump();
606        }
607        if (searchAttr == null || searchAttr.length() == 0) {
608            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
609        }
610        if (searchAttr.compareTo("0") != 0 && searchAttr.compareTo("1") != 0) {
611            Log.w(TAG, "search attr not supported");
612            if (searchAttr.compareTo("2") == 0) {
613                // search by sound is not supported currently
614                Log.w(TAG, "do not support search by sound");
615                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
616            }
617            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
618        }
619
620        ApplicationParameter ap = new ApplicationParameter();
621        if ((appParamValue.needTag & NEED_PB_SIZE) == NEED_PB_SIZE) {
622            int size = 0;
623            byte[] pbsize = new byte[2];
624            if ((appParamValue.needTag & NEED_PHONEBOOK) == NEED_PHONEBOOK) {
625                size = BluetoothPbapService.getPhonebookSize(NEED_PHONEBOOK);
626            } else if ((appParamValue.needTag & NEED_INCOMING_CALL_NUMBER) == NEED_INCOMING_CALL_NUMBER) {
627                size = BluetoothPbapService.getPhonebookSize(NEED_INCOMING_CALL_NUMBER);
628            } else if ((appParamValue.needTag & NEED_OUTGOING_CALL_NUMBER) == NEED_OUTGOING_CALL_NUMBER) {
629                size = BluetoothPbapService.getPhonebookSize(NEED_OUTGOING_CALL_NUMBER);
630            } else if ((appParamValue.needTag & NEED_COMBINED_CALL_NUMBER) == NEED_COMBINED_CALL_NUMBER) {
631                size = BluetoothPbapService.getPhonebookSize(NEED_COMBINED_CALL_NUMBER);
632            } else if ((appParamValue.needTag & NEED_MISSED_CALL_NUMBER) == NEED_MISSED_CALL_NUMBER) {
633                size = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER);
634                mMissedCallSize = size;
635            }
636            pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
637            pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
638            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
639                    ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
640            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
641            return pushHeader(op, reply);
642        }
643
644        if ((appParamValue.needTag & NEED_NEW_MISSED_CALL_NUMBER) == NEED_NEW_MISSED_CALL_NUMBER) {
645            byte[] misnum = new byte[1];
646            int nmnum = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER)
647                    - mMissedCallSize;
648            nmnum = nmnum > 0 ? nmnum : 0;
649            misnum[0] = (byte)nmnum;
650            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
651                    ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
652            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
653            return pushHeader(op, reply);
654        }
655
656        else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
657                && ((appParamValue.needTag & NEED_PHONEBOOK) == NEED_PHONEBOOK)) {
658            if (createVcardListingXml(NEED_PHONEBOOK, op, appParamValue.maxListCount,
659                    appParamValue.listStartOffset, appParamValue.searchValue,
660                    appParamValue.searchAttr) <= 0) {
661                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
662            }
663            return ResponseCodes.OBEX_HTTP_OK;
664        }
665
666        else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
667                && ((appParamValue.needTag & NEED_INCOMING_CALL_NUMBER)
668                        == NEED_INCOMING_CALL_NUMBER)) {
669            if (createVcardListingXml(NEED_INCOMING_CALL_NUMBER, op, appParamValue.maxListCount,
670                    appParamValue.listStartOffset, null, null) <= 0) {
671                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
672            }
673            return ResponseCodes.OBEX_HTTP_OK;
674        }
675
676        else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
677                && ((appParamValue.needTag & NEED_OUTGOING_CALL_NUMBER)
678                        == NEED_OUTGOING_CALL_NUMBER)) {
679            if (createVcardListingXml(NEED_OUTGOING_CALL_NUMBER, op, appParamValue.maxListCount,
680                    appParamValue.listStartOffset, null, null) <= 0) {
681                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
682            }
683            return ResponseCodes.OBEX_HTTP_OK;
684        }
685
686        else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
687                && ((appParamValue.needTag & NEED_COMBINED_CALL_NUMBER)
688                        == NEED_COMBINED_CALL_NUMBER)) {
689            if (createVcardListingXml(NEED_COMBINED_CALL_NUMBER, op, appParamValue.maxListCount,
690                    appParamValue.listStartOffset, null, null) <= 0) {
691                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
692            }
693            return ResponseCodes.OBEX_HTTP_OK;
694        }
695
696        else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
697                && ((appParamValue.needTag & NEED_MISSED_CALL_NUMBER)
698                        == NEED_MISSED_CALL_NUMBER)) {
699            if (createVcardListingXml(NEED_MISSED_CALL_NUMBER, op, appParamValue.maxListCount,
700                    appParamValue.listStartOffset, null, null) <= 0) {
701                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
702            }
703            return ResponseCodes.OBEX_HTTP_OK;
704        }
705        return ResponseCodes.OBEX_HTTP_OK;
706    }
707
708    private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue,
709            final Operation op, final String name, final String current_path) {
710        boolean vcard21 = true;
711        if (!parseApplicationParameter(appParam, appParamValue))
712            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
713        String str = "";
714        String strIndex = "";
715        int intIndex = 0;
716        if (name == null || name.length() < VCARD_NAME_MIN_LEN) {
717            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
718        }
719        strIndex = name.substring(0, name.length() - VCARD_NAME_MIN_LEN + 1);
720        if (strIndex.trim().length() != 0) {
721            try {
722                intIndex = Integer.parseInt(strIndex);
723            } catch (NumberFormatException e) {
724                Log.e(TAG, "catch number format exception " + e.toString());
725                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
726            }
727        }
728        if (current_path.compareTo(PB_PATH) == 0) {
729            if (intIndex < 0 || intIndex >= BluetoothPbapService.getPhonebookSize(NEED_PHONEBOOK))
730                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
731            if (intIndex >= 0)
732                str = BluetoothPbapService.getPhonebook(NEED_PHONEBOOK, intIndex, vcard21);
733        } else if (current_path.compareTo(ICH_PATH) == 0) {
734            if (intIndex <= 0
735                    || intIndex > BluetoothPbapService.getPhonebookSize(NEED_INCOMING_CALL_NUMBER))
736                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
737            if (intIndex >= 1)
738                str = BluetoothPbapService.getPhonebook(NEED_INCOMING_CALL_NUMBER, intIndex - 1,
739                        vcard21);
740        } else if (current_path.compareTo(OCH_PATH) == 0) {
741            if (intIndex <= 0
742                    || intIndex > BluetoothPbapService.getPhonebookSize(NEED_OUTGOING_CALL_NUMBER))
743                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
744            if (intIndex >= 1)
745                str = BluetoothPbapService.getPhonebook(NEED_OUTGOING_CALL_NUMBER, intIndex - 1,
746                        vcard21);
747        } else if (current_path.compareTo(CCH_PATH) == 0) {
748            if (intIndex == 0)
749                return ResponseCodes.OBEX_HTTP_OK;
750            if (intIndex < 0
751                    || intIndex > BluetoothPbapService.getPhonebookSize(NEED_COMBINED_CALL_NUMBER))
752                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
753            if (intIndex >= 1)
754                str = BluetoothPbapService.getPhonebook(NEED_COMBINED_CALL_NUMBER, intIndex - 1,
755                        vcard21);
756        } else if (current_path.compareTo(MCH_PATH) == 0) {
757            if (intIndex <= 0
758                    || intIndex > BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER))
759                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
760            if (intIndex >= 1)
761                str = BluetoothPbapService.getPhonebook(NEED_MISSED_CALL_NUMBER, intIndex - 1,
762                        vcard21);
763        } else {
764            Log.w(TAG, "wrong path!");
765            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
766        }
767        return pushBytes(op, str);
768    }
769
770    private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
771            final Operation op, final String name) {
772        boolean vcard21 = true;
773        String str;
774        String strstr = "";
775        int pbSize = 0;
776        int size = 0;
777        int pos = 0;
778        // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
779        if (name != null) {
780            int dotIndex = name.indexOf(".");
781            String vcf = "vcf";
782            if (dotIndex >= 0 && dotIndex <= name.length()) {
783                if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
784                    Log.w(TAG, "name is not .vcf");
785                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
786                }
787            }
788        }
789        // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
790        if (!parseApplicationParameter(appParam, appParamValue)) {
791            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
792        }
793        // check whether to send application response parameter to client
794        ApplicationParameter ap = new ApplicationParameter();
795        if ((appParamValue.needTag & NEED_PB_SIZE) == NEED_PB_SIZE) {
796            byte[] pbsize = new byte[2];
797            if ((appParamValue.needTag & NEED_PHONEBOOK) == NEED_PHONEBOOK) {
798                size = BluetoothPbapService.getPhonebookSize(NEED_PHONEBOOK);
799            } else if ((appParamValue.needTag & NEED_INCOMING_CALL_NUMBER)
800                    == NEED_INCOMING_CALL_NUMBER) {
801                size = BluetoothPbapService.getPhonebookSize(NEED_INCOMING_CALL_NUMBER);
802            } else if ((appParamValue.needTag & NEED_OUTGOING_CALL_NUMBER)
803                    == NEED_OUTGOING_CALL_NUMBER) {
804                size = BluetoothPbapService.getPhonebookSize(NEED_OUTGOING_CALL_NUMBER);
805            } else if ((appParamValue.needTag & NEED_COMBINED_CALL_NUMBER)
806                    == NEED_COMBINED_CALL_NUMBER) {
807                size = BluetoothPbapService.getPhonebookSize(NEED_COMBINED_CALL_NUMBER);
808            } else if ((appParamValue.needTag & NEED_MISSED_CALL_NUMBER)
809                    == NEED_MISSED_CALL_NUMBER) {
810                size = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER);
811                mMissedCallSize = size;
812            }
813            pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
814            pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
815            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
816                    ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
817            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
818            return pushHeader(op, reply);
819        }
820
821        if ((appParamValue.needTag & NEED_NEW_MISSED_CALL_NUMBER) == NEED_NEW_MISSED_CALL_NUMBER) {
822            byte[] misnum = new byte[1];
823            int nmnum = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER)
824                    - mMissedCallSize;
825            nmnum = nmnum > 0 ? nmnum : 0;
826            misnum[0] = (byte)nmnum;
827            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
828                    ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
829            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
830            return pushHeader(op, reply);
831        }
832
833        if ((appParamValue.needTag & NEED_PHONEBOOK) == NEED_PHONEBOOK) {
834            pbSize = BluetoothPbapService.getPhonebookSize(NEED_PHONEBOOK);
835            size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
836            for (pos = appParamValue.listStartOffset; pos < size; pos++) {
837                str = BluetoothPbapService.getPhonebook(NEED_PHONEBOOK, pos, vcard21);
838                strstr += str;
839            }
840        }
841
842        if ((appParamValue.needTag & NEED_INCOMING_CALL_NUMBER) == NEED_INCOMING_CALL_NUMBER) {
843            pbSize = BluetoothPbapService.getPhonebookSize(NEED_INCOMING_CALL_NUMBER);
844            size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
845            for (pos = appParamValue.listStartOffset; pos < size; pos++) {
846                str = BluetoothPbapService.getPhonebook(NEED_INCOMING_CALL_NUMBER, pos, vcard21);
847                strstr += str;
848            }
849        }
850
851        if ((appParamValue.needTag & NEED_OUTGOING_CALL_NUMBER) == NEED_OUTGOING_CALL_NUMBER) {
852            pbSize = BluetoothPbapService.getPhonebookSize(NEED_OUTGOING_CALL_NUMBER);
853            size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
854            for (pos = appParamValue.listStartOffset; pos < size; pos++) {
855                str = BluetoothPbapService.getPhonebook(NEED_OUTGOING_CALL_NUMBER, pos, vcard21);
856                strstr += str;
857            }
858        }
859
860        if ((appParamValue.needTag & NEED_COMBINED_CALL_NUMBER) == NEED_COMBINED_CALL_NUMBER) {
861            pbSize = BluetoothPbapService.getPhonebookSize(NEED_COMBINED_CALL_NUMBER);
862            size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
863            for (pos = appParamValue.listStartOffset; pos < size; pos++) {
864                str = BluetoothPbapService.getPhonebook(NEED_COMBINED_CALL_NUMBER, pos, vcard21);
865                strstr += str;
866            }
867        }
868
869        if ((appParamValue.needTag & NEED_MISSED_CALL_NUMBER) == NEED_MISSED_CALL_NUMBER) {
870            pbSize = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER);
871            size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
872            for (pos = appParamValue.listStartOffset; pos < size; pos++) {
873                str = BluetoothPbapService.getPhonebook(NEED_MISSED_CALL_NUMBER, pos, vcard21);
874                strstr += str;
875            }
876        }
877        return pushBytes(op, strstr);
878    }
879
880    private boolean closeStream(final OutputStream out, final Operation op) {
881        boolean returnvalue = true;
882        try {
883            if (out != null) {
884                out.close();
885            }
886        } catch (IOException e) {
887            Log.e(TAG, "outputStream close failed" + e.toString());
888            returnvalue = false;
889        }
890        try {
891            if (op != null) {
892                op.close();
893            }
894        } catch (IOException e) {
895            Log.e(TAG, "oeration close failed" + e.toString());
896            returnvalue = false;
897        }
898        return returnvalue;
899    }
900
901    public final void setConnectionID(final long id) {
902        if ((id < -1) || (id > 0xFFFFFFFFL)) {
903            throw new IllegalArgumentException("Illegal Connection ID");
904        }
905        mConnectionId = id;
906    }
907
908    /**
909     * Retrieves the connection ID that is being used in the present connection.
910     * This method will return -1 if no connection ID is being used.
911     *
912     * @return the connection id being used or -1 if no connection ID is being
913     *         used
914     */
915    public final long getConnectionID() {
916        return mConnectionId;
917    }
918
919    // Reserved for future use.In case PSE challenge PCE and PCE input wrong
920    // session key.
921    public final void onAuthenticationFailure(final byte[] userName) {
922    }
923}
924