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