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