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