BluetoothPbapObexServer.java revision 76a12d7e679ae48f8ab73e01b33fb7c8f077a210
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.content.ContentResolver;
37import android.database.Cursor;
38import android.os.Message;
39import android.os.Handler;
40import android.provider.CallLog;
41import android.provider.CallLog.Calls;
42import android.text.TextUtils;
43import android.util.Log;
44
45import java.io.IOException;
46import java.io.OutputStream;
47import java.text.CharacterIterator;
48import java.text.StringCharacterIterator;
49import java.util.ArrayList;
50import java.util.Arrays;
51import java.nio.ByteBuffer;
52
53import javax.obex.ServerRequestHandler;
54import javax.obex.ResponseCodes;
55import javax.obex.ApplicationParameter;
56import javax.obex.Operation;
57import javax.obex.HeaderSet;
58
59public class BluetoothPbapObexServer extends ServerRequestHandler {
60
61    private static final String TAG = "BluetoothPbapObexServer";
62
63    private static final boolean D = BluetoothPbapService.DEBUG;
64
65    private static final boolean V = BluetoothPbapService.VERBOSE;
66
67    private static final int UUID_LENGTH = 16;
68
69    public static final long INVALID_VALUE_PARAMETER = -1;
70
71    // The length of suffix of vcard name - ".vcf" is 5
72    private static final int VCARD_NAME_SUFFIX_LENGTH = 5;
73
74    // 128 bit UUID for PBAP
75    private static final byte[] PBAP_TARGET = new byte[] {
76            0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66,
77            0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66
78    };
79
80    // Currently not support SIM card
81    private static final String[] LEGAL_PATH = {
82            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
83            "/telecom/cch"
84    };
85
86    @SuppressWarnings("unused")
87    private static final String[] LEGAL_PATH_WITH_SIM = {
88            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
89            "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och",
90            "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb"
91
92    };
93
94    // SIM card
95    private static final String SIM1 = "SIM1";
96
97    // missed call history
98    private static final String MCH = "mch";
99
100    // incoming call history
101    private static final String ICH = "ich";
102
103    // outgoing call history
104    private static final String OCH = "och";
105
106    // combined call history
107    private static final String CCH = "cch";
108
109    // phone book
110    private static final String PB = "pb";
111
112    private static final String TELECOM_PATH = "/telecom";
113
114    private static final String ICH_PATH = "/telecom/ich";
115
116    private static final String OCH_PATH = "/telecom/och";
117
118    private static final String MCH_PATH = "/telecom/mch";
119
120    private static final String CCH_PATH = "/telecom/cch";
121
122    private static final String PB_PATH = "/telecom/pb";
123
124    // type for list vcard objects
125    private static final String TYPE_LISTING = "x-bt/vcard-listing";
126
127    // type for get single vcard object
128    private static final String TYPE_VCARD = "x-bt/vcard";
129
130    // to indicate if need send body besides headers
131    private static final int NEED_SEND_BODY = -1;
132
133    // type for download all vcard objects
134    private static final String TYPE_PB = "x-bt/phonebook";
135
136    // The number of indexes in the phone book.
137    private boolean mNeedPhonebookSize = false;
138
139    // The number of missed calls that have not been checked on the PSE at the
140    // point of the request. Only apply to "mch" case.
141    private boolean mNeedNewMissedCallsNum = false;
142
143    private boolean mVcardSelector = false;
144
145    private int mMissedCallSize = 0;
146
147    // record current path the client are browsing
148    private String mCurrentPath = "";
149
150    private Handler mCallback = null;
151
152    private Context mContext;
153
154    private BluetoothPbapService mService;
155
156    private BluetoothPbapVcardManager mVcardManager;
157
158    private int mOrderBy  = ORDER_BY_INDEXED;
159
160    private static final int CALLLOG_NUM_LIMIT = 50;
161
162    public static final int ORDER_BY_INDEXED = 0;
163
164    public static final int ORDER_BY_ALPHABETICAL = 1;
165
166    public static boolean sIsAborted = false;
167
168    private long mDatabaseIdentifierLow = INVALID_VALUE_PARAMETER;
169
170    private long mDatabaseIdentifierHigh = INVALID_VALUE_PARAMETER;
171
172    private long mFolderVersionCounterbitMask = 0x0008;
173
174    private long mDatabaseIdentifierBitMask = 0x0004;
175
176    private AppParamValue mConnAppParamValue;
177
178    public static class ContentType {
179        public static final int PHONEBOOK = 1;
180
181        public static final int INCOMING_CALL_HISTORY = 2;
182
183        public static final int OUTGOING_CALL_HISTORY = 3;
184
185        public static final int MISSED_CALL_HISTORY = 4;
186
187        public static final int COMBINED_CALL_HISTORY = 5;
188    }
189
190    public BluetoothPbapObexServer(Handler callback, BluetoothPbapService service) {
191        super();
192        mCallback = callback;
193        mService = service;
194        mContext = service.getApplicationContext();
195        mVcardManager = new BluetoothPbapVcardManager(mContext);
196    }
197
198    @Override
199    public int onConnect(final HeaderSet request, HeaderSet reply) {
200        if (V) logHeader(request);
201        notifyUpdateWakeLock();
202        try {
203            byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
204            if (uuid == null) {
205                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
206            }
207            if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
208
209            if (uuid.length != UUID_LENGTH) {
210                Log.w(TAG, "Wrong UUID length");
211                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
212            }
213            for (int i = 0; i < UUID_LENGTH; i++) {
214                if (uuid[i] != PBAP_TARGET[i]) {
215                    Log.w(TAG, "Wrong UUID");
216                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
217                }
218            }
219            reply.setHeader(HeaderSet.WHO, uuid);
220        } catch (IOException e) {
221            Log.e(TAG, e.toString());
222            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
223        }
224
225        try {
226            byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
227            if (remote != null) {
228                if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
229                reply.setHeader(HeaderSet.TARGET, remote);
230            }
231        } catch (IOException e) {
232            Log.e(TAG, e.toString());
233            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
234        }
235
236        try {
237            byte[] appParam = null;
238            mConnAppParamValue = new AppParamValue();
239            appParam = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER);
240            if ((appParam != null) && !parseApplicationParameter(appParam, mConnAppParamValue)) {
241                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
242            }
243        } catch (IOException e) {
244            Log.e(TAG, e.toString());
245            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
246        }
247
248        if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
249                "MSG_SESSION_ESTABLISHED msg.");
250
251        Message msg = Message.obtain(mCallback);
252        msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
253        msg.sendToTarget();
254        return ResponseCodes.OBEX_HTTP_OK;
255    }
256
257    @Override
258    public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
259        if (D) Log.d(TAG, "onDisconnect(): enter");
260        if (V) logHeader(req);
261        notifyUpdateWakeLock();
262        resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
263        if (mCallback != null) {
264            Message msg = Message.obtain(mCallback);
265            msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED;
266            msg.sendToTarget();
267            if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
268        }
269    }
270
271    @Override
272    public int onAbort(HeaderSet request, HeaderSet reply) {
273        if (D) Log.d(TAG, "onAbort(): enter.");
274        notifyUpdateWakeLock();
275        sIsAborted = true;
276        return ResponseCodes.OBEX_HTTP_OK;
277    }
278
279    @Override
280    public int onPut(final Operation op) {
281        if (D) Log.d(TAG, "onPut(): not support PUT request.");
282        notifyUpdateWakeLock();
283        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
284    }
285
286    @Override
287    public int onDelete(final HeaderSet request, final HeaderSet reply) {
288        if (D) Log.d(TAG, "onDelete(): not support PUT request.");
289        notifyUpdateWakeLock();
290        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
291    }
292
293    @Override
294    public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
295            final boolean create) {
296        if (V) logHeader(request);
297        if (D) Log.d(TAG, "before setPath, mCurrentPath ==  " + mCurrentPath);
298        notifyUpdateWakeLock();
299        String currentPathTmp = mCurrentPath;
300        String tmpPath = null;
301        try {
302            tmpPath = (String)request.getHeader(HeaderSet.NAME);
303        } catch (IOException e) {
304            Log.e(TAG, "Get name header fail");
305            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
306        }
307        if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmpPath);
308
309        if (backup) {
310            if (currentPathTmp.length() != 0) {
311                currentPathTmp = currentPathTmp.substring(0,
312                        currentPathTmp.lastIndexOf("/"));
313            }
314        } else {
315            if (tmpPath == null) {
316                currentPathTmp = "";
317            } else {
318                currentPathTmp = currentPathTmp + "/" + tmpPath;
319            }
320        }
321
322        if ((currentPathTmp.length() != 0) && (!isLegalPath(currentPathTmp))) {
323            if (create) {
324                Log.w(TAG, "path create is forbidden!");
325                return ResponseCodes.OBEX_HTTP_FORBIDDEN;
326            } else {
327                Log.w(TAG, "path is not legal");
328                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
329            }
330        }
331        mCurrentPath = currentPathTmp;
332        if (V) Log.v(TAG, "after setPath, mCurrentPath ==  " + mCurrentPath);
333
334        return ResponseCodes.OBEX_HTTP_OK;
335    }
336
337    @Override
338    public void onClose() {
339        if (mCallback != null) {
340            Message msg = Message.obtain(mCallback);
341            msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
342            msg.sendToTarget();
343            if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
344        }
345    }
346
347    @Override
348    public int onGet(Operation op) {
349        notifyUpdateWakeLock();
350        sIsAborted = false;
351        HeaderSet request = null;
352        HeaderSet reply = new HeaderSet();
353        String type = "";
354        String name = "";
355        byte[] appParam = null;
356        AppParamValue appParamValue = new AppParamValue();
357        try {
358            request = op.getReceivedHeader();
359            type = (String)request.getHeader(HeaderSet.TYPE);
360            name = (String)request.getHeader(HeaderSet.NAME);
361            appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
362        } catch (IOException e) {
363            Log.e(TAG, "request headers error");
364            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
365        }
366
367        /* TODO: block Get request if contacts are not completely loaded locally */
368
369        if (V) logHeader(request);
370        if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name);
371
372        if (type == null) {
373            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
374        }
375        // Accroding to specification,the name header could be omitted such as
376        // sony erriccsonHBH-DS980
377
378        // For "x-bt/phonebook" and "x-bt/vcard-listing":
379        // if name == null, guess what carkit actually want from current path
380        // For "x-bt/vcard":
381        // We decide which kind of content client would like per current path
382
383        boolean validName = true;
384        if (TextUtils.isEmpty(name)) {
385            validName = false;
386        }
387
388        if (!validName || (validName && type.equals(TYPE_VCARD))) {
389            if (D) Log.d(TAG, "Guess what carkit actually want from current path (" +
390                    mCurrentPath + ")");
391
392            if (mCurrentPath.equals(PB_PATH)) {
393                appParamValue.needTag = ContentType.PHONEBOOK;
394            } else if (mCurrentPath.equals(ICH_PATH)) {
395                appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
396            } else if (mCurrentPath.equals(OCH_PATH)) {
397                appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
398            } else if (mCurrentPath.equals(MCH_PATH)) {
399                appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
400                mNeedNewMissedCallsNum = true;
401            } else if (mCurrentPath.equals(CCH_PATH)) {
402                appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
403            } else if (mCurrentPath.equals(TELECOM_PATH)) {
404                /* PBAP 1.1.1 change */
405                if (!validName && type.equals(TYPE_LISTING)) {
406                    Log.e(TAG, "invalid vcard listing request in default folder");
407                    return ResponseCodes.OBEX_HTTP_NOT_FOUND;
408                }
409            } else {
410                Log.w(TAG, "mCurrentpath is not valid path!!!");
411                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
412            }
413            if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
414        } else {
415            // Not support SIM card currently
416            if (name.contains(SIM1.subSequence(0, SIM1.length()))) {
417                Log.w(TAG, "Not support access SIM card info!");
418                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
419            }
420
421            // we have weak name checking here to provide better
422            // compatibility with other devices,although unique name such as
423            // "pb.vcf" is required by SIG spec.
424            if (isNameMatchTarget(name, PB)) {
425                appParamValue.needTag = ContentType.PHONEBOOK;
426                if (D) Log.v(TAG, "download phonebook request");
427            } else if (isNameMatchTarget(name, ICH)) {
428                appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
429                appParamValue.callHistoryVersionCounter =
430                        mVcardManager.getCallHistoryPrimaryFolderVersion(
431                                ContentType.INCOMING_CALL_HISTORY);
432                if (D) Log.v(TAG, "download incoming calls request");
433            } else if (isNameMatchTarget(name, OCH)) {
434                appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
435                appParamValue.callHistoryVersionCounter =
436                        mVcardManager.getCallHistoryPrimaryFolderVersion(
437                                ContentType.OUTGOING_CALL_HISTORY);
438                if (D) Log.v(TAG, "download outgoing calls request");
439            } else if (isNameMatchTarget(name, MCH)) {
440                appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
441                appParamValue.callHistoryVersionCounter =
442                        mVcardManager.getCallHistoryPrimaryFolderVersion(
443                                ContentType.MISSED_CALL_HISTORY);
444                mNeedNewMissedCallsNum = true;
445                if (D) Log.v(TAG, "download missed calls request");
446            } else if (isNameMatchTarget(name, CCH)) {
447                appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
448                appParamValue.callHistoryVersionCounter =
449                        mVcardManager.getCallHistoryPrimaryFolderVersion(
450                                ContentType.COMBINED_CALL_HISTORY);
451                if (D) Log.v(TAG, "download combined calls request");
452            } else {
453                Log.w(TAG, "Input name doesn't contain valid info!!!");
454                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
455            }
456        }
457
458        if ((appParam != null) && !parseApplicationParameter(appParam, appParamValue)) {
459            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
460        }
461
462        // listing request
463        if (type.equals(TYPE_LISTING)) {
464            return pullVcardListing(appParam, appParamValue, reply, op, name);
465        } else if (type.equals(TYPE_VCARD)) {
466            // pull vcard entry request
467            return pullVcardEntry(appParam, appParamValue, op, reply, name, mCurrentPath);
468        } else if (type.equals(TYPE_PB)) {
469            // down load phone book request
470            return pullPhonebook(appParam, appParamValue, reply, op, name);
471        } else {
472            Log.w(TAG, "unknown type request!!!");
473            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
474        }
475    }
476
477    private boolean isNameMatchTarget(String name, String target) {
478        if (name == null) return false;
479        String contentTypeName = name;
480        if (contentTypeName.endsWith(".vcf")) {
481            contentTypeName = contentTypeName
482                    .substring(0, contentTypeName.length() - ".vcf".length());
483        }
484        // There is a test case: Client will send a wrong name "/telecom/pbpb".
485        // So we must use the String between '/' and '/' as a indivisible part
486        // for comparing.
487        String[] nameList = contentTypeName.split("/");
488        for (String subName : nameList) {
489            if (subName.equals(target)) {
490                return true;
491            }
492        }
493        return false;
494    }
495
496    /** check whether path is legal */
497    private boolean isLegalPath(final String str) {
498        if (str.length() == 0) {
499            return true;
500        }
501        for (int i = 0; i < LEGAL_PATH.length; i++) {
502            if (str.equals(LEGAL_PATH[i])) {
503                return true;
504            }
505        }
506        return false;
507    }
508
509    private class AppParamValue {
510        public int maxListCount;
511
512        public int listStartOffset;
513
514        public String searchValue;
515
516        // Indicate which vCard parameter the search operation shall be carried
517        // out on. Can be "Name | Number | Sound", default value is "Name".
518        public String searchAttr;
519
520        // Indicate which sorting order shall be used for the
521        // <x-bt/vcard-listing> listing object.
522        // Can be "Alphabetical | Indexed | Phonetical", default value is
523        // "Indexed".
524        public String order;
525
526        public int needTag;
527
528        public boolean vcard21;
529
530        public byte[] propertySelector;
531
532        public byte[] supportedFeature;
533
534        public boolean ignorefilter;
535
536        public byte[] vCardSelector;
537
538        public String vCardSelectorOperator;
539
540        public byte[] callHistoryVersionCounter;
541
542        AppParamValue() {
543            maxListCount = 0xFFFF;
544            listStartOffset = 0;
545            searchValue = "";
546            searchAttr = "";
547            order = "";
548            needTag = 0x00;
549            vcard21 = true;
550            //Filter is not set by default
551            ignorefilter = true;
552            vCardSelectorOperator = "0";
553            propertySelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
554            vCardSelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
555            supportedFeature = new byte[] {0x00, 0x00, 0x00, 0x00};
556        }
557
558        public void dump() {
559            Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
560                            + " searchValue=" + searchValue + " searchAttr=" + searchAttr
561                            + " needTag=" + needTag + " vcard21=" + vcard21 + " order=" + order
562                            + "vcardselector=" + vCardSelector + "vcardselop="
563                            + vCardSelectorOperator);
564        }
565    }
566
567    /** To parse obex application parameter */
568    private boolean parseApplicationParameter(final byte[] appParam,
569            AppParamValue appParamValue) {
570        int i = 0;
571        boolean parseOk = true;
572        while ((i < appParam.length) && (parseOk)) {
573            switch (appParam[i]) {
574                case ApplicationParameter.TRIPLET_TAGID.PROPERTY_SELECTOR_TAGID:
575                    i += 2; // length and tag field in triplet
576                    for (int index = 0;
577                            index < ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH;
578                            index++) {
579                        if (appParam[i+index] != 0){
580                            appParamValue.ignorefilter = false;
581                            appParamValue.propertySelector[index] = appParam[i + index];
582                        }
583                    }
584                    i += ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH;
585                    break;
586                case ApplicationParameter.TRIPLET_TAGID.SUPPORTEDFEATURE_TAGID:
587                    i += 2; // length and tag field in triplet
588                    for (int index = 0;
589                            index < ApplicationParameter.TRIPLET_LENGTH.SUPPORTEDFEATURE_LENGTH;
590                            index++) {
591                        if (appParam[i + index] != 0) {
592                            appParamValue.supportedFeature[index] = appParam[i + index];
593                        }
594                    }
595
596                    i += ApplicationParameter.TRIPLET_LENGTH.SUPPORTEDFEATURE_LENGTH;
597                    break;
598
599                case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID:
600                    i += 2; // length and tag field in triplet
601                    appParamValue.order = Byte.toString(appParam[i]);
602                    i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH;
603                    break;
604                case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID:
605                    i += 1; // length field in triplet
606                    // length of search value is variable
607                    int length = appParam[i];
608                    if (length == 0) {
609                        parseOk = false;
610                        break;
611                    }
612                    if (appParam[i+length] == 0x0) {
613                        appParamValue.searchValue = new String(appParam, i + 1, length-1);
614                    } else {
615                        appParamValue.searchValue = new String(appParam, i + 1, length);
616                    }
617                    i += length;
618                    i += 1;
619                    break;
620                case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
621                    i += 2;
622                    appParamValue.searchAttr = Byte.toString(appParam[i]);
623                    i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
624                    break;
625                case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
626                    i += 2;
627                    if (appParam[i] == 0 && appParam[i + 1] == 0) {
628                        mNeedPhonebookSize = true;
629                    } else {
630                        int highValue = appParam[i] & 0xff;
631                        int lowValue = appParam[i + 1] & 0xff;
632                        appParamValue.maxListCount = highValue * 256 + lowValue;
633                    }
634                    i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
635                    break;
636                case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
637                    i += 2;
638                    int highValue = appParam[i] & 0xff;
639                    int lowValue = appParam[i + 1] & 0xff;
640                    appParamValue.listStartOffset = highValue * 256 + lowValue;
641                    i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
642                    break;
643                case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
644                    i += 2; // length field in triplet
645                    if (appParam[i] != 0) {
646                        appParamValue.vcard21 = false;
647                    }
648                    i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
649                    break;
650
651                case ApplicationParameter.TRIPLET_TAGID.VCARDSELECTOR_TAGID:
652                    i += 2;
653                    for (int index = 0;
654                            index < ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOR_LENGTH;
655                            index++) {
656                        if (appParam[i + index] != 0) {
657                            mVcardSelector = true;
658                            appParamValue.vCardSelector[index] = appParam[i + index];
659                        }
660                    }
661                    i += ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOR_LENGTH;
662                    break;
663                case ApplicationParameter.TRIPLET_TAGID.VCARDSELECTOROPERATOR_TAGID:
664                    i += 2;
665                    appParamValue.vCardSelectorOperator = Byte.toString(appParam[i]);
666                    i += ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOROPERATOR_LENGTH;
667                    break;
668                default:
669                    parseOk = false;
670                    Log.e(TAG, "Parse Application Parameter error");
671                    break;
672            }
673        }
674
675        if (D) appParamValue.dump();
676
677        return parseOk;
678    }
679
680    /** Form and Send an XML format String to client for Phone book listing */
681    private int sendVcardListingXml(
682            AppParamValue appParamValue, Operation op, int needSendBody, int size) {
683        StringBuilder result = new StringBuilder();
684        int itemsFound = 0;
685        result.append("<?xml version=\"1.0\"?>");
686        result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
687        result.append("<vCard-listing version=\"1.0\">");
688
689        // Phonebook listing request
690        if (appParamValue.needTag == ContentType.PHONEBOOK) {
691            String type = "";
692            if (appParamValue.searchAttr.equals("0")) {
693                type = "name";
694            } else if (appParamValue.searchAttr.equals("1")) {
695                type = "number";
696            }
697            if (type.length() > 0) {
698                itemsFound = createList(appParamValue, needSendBody, size, result, type);
699            } else {
700                return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
701            }
702        } else {
703            // Call history listing request
704            ArrayList<String> nameList = mVcardManager.loadCallHistoryList(appParamValue.needTag);
705            int requestSize = nameList.size() >= appParamValue.maxListCount
706                    ? appParamValue.maxListCount
707                    : nameList.size();
708            int startPoint = appParamValue.listStartOffset;
709            int endPoint = startPoint + requestSize;
710            if (endPoint > nameList.size()) {
711                endPoint = nameList.size();
712            }
713            if (D)
714                Log.d(TAG, "call log list, size=" + requestSize + " offset="
715                                + appParamValue.listStartOffset);
716
717            for (int j = startPoint; j < endPoint; j++) {
718                writeVCardEntry(j+1, nameList.get(j),result);
719            }
720        }
721        result.append("</vCard-listing>");
722
723        if (D) Log.d(TAG, "itemsFound =" + itemsFound);
724
725        return pushBytes(op, result.toString());
726    }
727
728    private int createList(AppParamValue appParamValue, int needSendBody, int size,
729            StringBuilder result, String type) {
730        int itemsFound = 0;
731
732        ArrayList<String> nameList = null;
733        if (mVcardSelector) {
734            nameList = mVcardManager.getSelectedPhonebookNameList(mOrderBy, appParamValue.vcard21,
735                    needSendBody, size, appParamValue.vCardSelector,
736                    appParamValue.vCardSelectorOperator);
737        } else {
738            nameList = mVcardManager.getPhonebookNameList(mOrderBy);
739        }
740
741        final int requestSize = nameList.size() >= appParamValue.maxListCount
742                ? appParamValue.maxListCount
743                : nameList.size();
744        final int listSize = nameList.size();
745        String compareValue = "", currentValue;
746
747        if (D)
748            Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset="
749                            + appParamValue.listStartOffset + " searchValue="
750                            + appParamValue.searchValue);
751
752        if (type.equals("number")) {
753            // query the number, to get the names
754            ArrayList<String> names =
755                    mVcardManager.getContactNamesByNumber(appParamValue.searchValue);
756            for (int i = 0; i < names.size(); i++) {
757                compareValue = names.get(i).trim();
758                if (D) Log.d(TAG, "compareValue=" + compareValue);
759                for (int pos = appParamValue.listStartOffset;
760                        pos < listSize && itemsFound < requestSize; pos++) {
761                    currentValue = nameList.get(pos);
762                    if (V) Log.d(TAG, "currentValue=" + currentValue);
763                    if (currentValue.equals(compareValue)) {
764                        itemsFound++;
765                        if (currentValue.contains(","))
766                           currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
767                        writeVCardEntry(pos, currentValue,result);
768                    }
769                }
770                if (itemsFound >= requestSize) {
771                    break;
772                }
773            }
774        } else {
775            if (appParamValue.searchValue != null) {
776                compareValue = appParamValue.searchValue.trim().toLowerCase();
777            }
778            for (int pos = appParamValue.listStartOffset;
779                    pos < listSize && itemsFound < requestSize; pos++) {
780                currentValue = nameList.get(pos);
781                if (currentValue.contains(","))
782                    currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
783
784                if (appParamValue.searchValue.isEmpty()
785                        || ((currentValue.toLowerCase()).startsWith(compareValue))) {
786                    itemsFound++;
787                    writeVCardEntry(pos, currentValue,result);
788                }
789            }
790        }
791        return itemsFound;
792    }
793
794    /**
795     * Function to send obex header back to client such as get phonebook size
796     * request
797     */
798    private int pushHeader(final Operation op, final HeaderSet reply) {
799        OutputStream outputStream = null;
800
801        if (D) Log.d(TAG, "Push Header");
802        if (D) Log.d(TAG, reply.toString());
803
804        int pushResult = ResponseCodes.OBEX_HTTP_OK;
805        try {
806            op.sendHeaders(reply);
807            outputStream = op.openOutputStream();
808            outputStream.flush();
809        } catch (IOException e) {
810            Log.e(TAG, e.toString());
811            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
812        } finally {
813            if (!closeStream(outputStream, op)) {
814                pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
815            }
816        }
817        return pushResult;
818    }
819
820    /** Function to send vcard data to client */
821    private int pushBytes(Operation op, final String vcardString) {
822        if (vcardString == null) {
823            Log.w(TAG, "vcardString is null!");
824            return ResponseCodes.OBEX_HTTP_OK;
825        }
826
827        OutputStream outputStream = null;
828        int pushResult = ResponseCodes.OBEX_HTTP_OK;
829        try {
830            outputStream = op.openOutputStream();
831            outputStream.write(vcardString.getBytes());
832            if (V) Log.v(TAG, "Send Data complete!");
833        } catch (IOException e) {
834            Log.e(TAG, "open/write outputstrem failed" + e.toString());
835            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
836        }
837
838        if (!closeStream(outputStream, op)) {
839            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
840        }
841
842        return pushResult;
843    }
844
845    private int handleAppParaForResponse(
846            AppParamValue appParamValue, int size, HeaderSet reply, Operation op, String name) {
847        byte[] misnum = new byte[1];
848        ApplicationParameter ap = new ApplicationParameter();
849        boolean needSendCallHistoryVersionCounters = false;
850        if (isNameMatchTarget(name, MCH) || isNameMatchTarget(name, ICH)
851                || isNameMatchTarget(name, OCH) || isNameMatchTarget(name, CCH))
852            needSendCallHistoryVersionCounters =
853                    checkPbapFeatureSupport(mFolderVersionCounterbitMask);
854        boolean needSendPhonebookVersionCounters = false;
855        if (isNameMatchTarget(name, PB))
856            needSendPhonebookVersionCounters = checkPbapFeatureSupport(mFolderVersionCounterbitMask);
857
858        // In such case, PCE only want the number of index.
859        // So response not contain any Body header.
860        if (mNeedPhonebookSize) {
861            if (D) Log.d(TAG, "Need Phonebook size in response header.");
862            mNeedPhonebookSize = false;
863
864            byte[] pbsize = new byte[2];
865
866            pbsize[0] = (byte) ((size / 256) & 0xff); // HIGH VALUE
867            pbsize[1] = (byte) ((size % 256) & 0xff); // LOW VALUE
868            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
869                    ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
870
871            if (mNeedNewMissedCallsNum) {
872                mNeedNewMissedCallsNum = false;
873                int nmnum = 0;
874                ContentResolver contentResolver;
875                contentResolver = mContext.getContentResolver();
876
877                Cursor c = contentResolver.query(
878                    Calls.CONTENT_URI,
879                    null,
880                    Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1",
881                    null,
882                    Calls.DEFAULT_SORT_ORDER);
883
884                if (c != null) {
885                  nmnum = c.getCount();
886                  c.close();
887                }
888
889                nmnum = nmnum > 0 ? nmnum : 0;
890                misnum[0] = (byte)nmnum;
891                if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
892            }
893
894            if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
895                setDbCounters(ap);
896            }
897            if (needSendPhonebookVersionCounters) {
898                setFolderVersionCounters(ap);
899            }
900            if (needSendCallHistoryVersionCounters) {
901                setCallversionCounters(ap, appParamValue);
902            }
903            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
904
905            if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
906
907            return pushHeader(op, reply);
908        }
909
910        // Only apply to "mch" download/listing.
911        // NewMissedCalls is used only in the response, together with Body
912        // header.
913        if (mNeedNewMissedCallsNum) {
914            if (D) Log.d(TAG, "Need new missed call num in response header.");
915            mNeedNewMissedCallsNum = false;
916            int nmnum = 0;
917            ContentResolver contentResolver;
918            contentResolver = mContext.getContentResolver();
919
920            Cursor c = contentResolver.query(
921                Calls.CONTENT_URI,
922                null,
923                Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1",
924                null,
925                Calls.DEFAULT_SORT_ORDER);
926
927            if (c != null) {
928              nmnum = c.getCount();
929              c.close();
930            }
931
932            nmnum = nmnum > 0 ? nmnum : 0;
933            misnum[0] = (byte)nmnum;
934            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
935
936            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
937                    ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
938            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
939            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
940                        + nmnum);
941
942            // Only Specifies the headers, not write for now, will write to PCE
943            // together with Body
944            try {
945                op.sendHeaders(reply);
946            } catch (IOException e) {
947                Log.e(TAG, e.toString());
948                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
949            }
950        }
951
952        if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
953            setDbCounters(ap);
954            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
955            try {
956                op.sendHeaders(reply);
957            } catch (IOException e) {
958                Log.e(TAG, e.toString());
959                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
960            }
961        }
962
963        if (needSendPhonebookVersionCounters) {
964            setFolderVersionCounters(ap);
965            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
966            try {
967                op.sendHeaders(reply);
968            } catch (IOException e) {
969                Log.e(TAG, e.toString());
970                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
971            }
972        }
973
974        if (needSendCallHistoryVersionCounters) {
975            setCallversionCounters(ap, appParamValue);
976            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
977            try {
978                op.sendHeaders(reply);
979            } catch (IOException e) {
980                Log.e(TAG, e.toString());
981                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
982            }
983        }
984
985        return NEED_SEND_BODY;
986    }
987
988    private int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
989            HeaderSet reply, Operation op, String name) {
990        String searchAttr = appParamValue.searchAttr.trim();
991
992        if (searchAttr == null || searchAttr.length() == 0) {
993            // If searchAttr is not set by PCE, set default value per spec.
994            appParamValue.searchAttr = "0";
995            if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
996        } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
997            Log.w(TAG, "search attr not supported");
998            if (searchAttr.equals("2")) {
999                // search by sound is not supported currently
1000                Log.w(TAG, "do not support search by sound");
1001                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1002            }
1003            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1004        } else {
1005            Log.i(TAG, "searchAttr is valid: " + searchAttr);
1006        }
1007
1008        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1009        int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1010        if (needSendBody != NEED_SEND_BODY) {
1011            op.noBodyHeader();
1012            return needSendBody;
1013        }
1014
1015        if (size == 0) {
1016            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1017            return ResponseCodes.OBEX_HTTP_OK;
1018        }
1019
1020        String orderPara = appParamValue.order.trim();
1021        if (TextUtils.isEmpty(orderPara)) {
1022            // If order parameter is not set by PCE, set default value per spec.
1023            orderPara = "0";
1024            if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
1025                       "Assume order by 'Indexed' by default");
1026        } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
1027            if (D) Log.d(TAG, "Order parameter is not supported: " + appParamValue.order);
1028            if (orderPara.equals("2")) {
1029                // Order by sound is not supported currently
1030                Log.w(TAG, "Do not support order by sound");
1031                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1032            }
1033            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1034        } else {
1035            Log.i(TAG, "Order parameter is valid: " + orderPara);
1036        }
1037
1038        if (orderPara.equals("0")) {
1039            mOrderBy = ORDER_BY_INDEXED;
1040        } else if (orderPara.equals("1")) {
1041            mOrderBy = ORDER_BY_ALPHABETICAL;
1042        }
1043
1044        return sendVcardListingXml(appParamValue, op, needSendBody, size);
1045    }
1046
1047    private int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op,
1048            HeaderSet reply, final String name, final String currentPath) {
1049        if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
1050            if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
1051            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1052        }
1053        String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
1054        int intIndex = 0;
1055        if (strIndex.trim().length() != 0) {
1056            try {
1057                intIndex = Integer.parseInt(strIndex);
1058            } catch (NumberFormatException e) {
1059                Log.e(TAG, "catch number format exception " + e.toString());
1060                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1061            }
1062        }
1063
1064        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1065        int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1066        if (size == 0) {
1067            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1068            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1069        }
1070
1071        boolean vcard21 = appParamValue.vcard21;
1072        if (appParamValue.needTag == 0) {
1073            Log.w(TAG, "wrong path!");
1074            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1075        } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
1076            if (intIndex < 0 || intIndex >= size) {
1077                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1078                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1079            } else if (intIndex == 0) {
1080                // For PB_PATH, 0.vcf is the phone number of this phone.
1081                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null);
1082                return pushBytes(op, ownerVcard);
1083            } else {
1084                return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
1085                        mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector);
1086            }
1087        } else {
1088            if (intIndex <= 0 || intIndex > size) {
1089                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1090                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1091            }
1092            // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
1093            // begin from 1.vcf
1094            if (intIndex >= 1) {
1095                return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1096                        intIndex, intIndex, vcard21, needSendBody, size, appParamValue.ignorefilter,
1097                        appParamValue.propertySelector, appParamValue.vCardSelector,
1098                        appParamValue.vCardSelectorOperator, mVcardSelector);
1099            }
1100        }
1101        return ResponseCodes.OBEX_HTTP_OK;
1102    }
1103
1104    private int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
1105            Operation op, final String name) {
1106        // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
1107        if (name != null) {
1108            int dotIndex = name.indexOf(".");
1109            String vcf = "vcf";
1110            if (dotIndex >= 0 && dotIndex <= name.length()) {
1111                if (!name.regionMatches(dotIndex + 1, vcf, 0, vcf.length())) {
1112                    Log.w(TAG, "name is not .vcf");
1113                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1114                }
1115            }
1116        } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
1117
1118        int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
1119        int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name);
1120        if (needSendBody != NEED_SEND_BODY) {
1121            op.noBodyHeader();
1122            return needSendBody;
1123        }
1124
1125        if (pbSize == 0) {
1126            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1127            return ResponseCodes.OBEX_HTTP_OK;
1128        }
1129
1130        int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
1131                : pbSize;
1132        int startPoint = appParamValue.listStartOffset;
1133        if (startPoint < 0 || startPoint >= pbSize) {
1134            Log.w(TAG, "listStartOffset is not correct! " + startPoint);
1135            return ResponseCodes.OBEX_HTTP_OK;
1136        }
1137
1138        // Limit the number of call log to CALLLOG_NUM_LIMIT
1139        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1140            if (requestSize > CALLLOG_NUM_LIMIT) {
1141               requestSize = CALLLOG_NUM_LIMIT;
1142            }
1143        }
1144
1145        int endPoint = startPoint + requestSize - 1;
1146        if (endPoint > pbSize - 1) {
1147            endPoint = pbSize - 1;
1148        }
1149        if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
1150                startPoint + " endPoint=" + endPoint);
1151
1152        boolean vcard21 = appParamValue.vcard21;
1153        if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1154            if (startPoint == 0) {
1155                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null);
1156                if (endPoint == 0) {
1157                    return pushBytes(op, ownerVcard);
1158                } else {
1159                    return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
1160                            ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter,
1161                            appParamValue.propertySelector, appParamValue.vCardSelector,
1162                            appParamValue.vCardSelectorOperator, mVcardSelector);
1163                }
1164            } else {
1165                return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
1166                        vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter,
1167                        appParamValue.propertySelector, appParamValue.vCardSelector,
1168                        appParamValue.vCardSelectorOperator, mVcardSelector);
1169            }
1170        } else {
1171            return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1172                    startPoint + 1, endPoint + 1, vcard21, needSendBody, pbSize,
1173                    appParamValue.ignorefilter, appParamValue.propertySelector,
1174                    appParamValue.vCardSelector, appParamValue.vCardSelectorOperator,
1175                    mVcardSelector);
1176        }
1177    }
1178
1179    public static boolean closeStream(final OutputStream out, final Operation op) {
1180        boolean returnvalue = true;
1181        try {
1182            if (out != null) {
1183                out.close();
1184            }
1185        } catch (IOException e) {
1186            Log.e(TAG, "outputStream close failed" + e.toString());
1187            returnvalue = false;
1188        }
1189        try {
1190            if (op != null) {
1191                op.close();
1192            }
1193        } catch (IOException e) {
1194            Log.e(TAG, "operation close failed" + e.toString());
1195            returnvalue = false;
1196        }
1197        return returnvalue;
1198    }
1199
1200    // Reserved for future use. In case PSE challenge PCE and PCE input wrong
1201    // session key.
1202    @Override
1203    public final void onAuthenticationFailure(final byte[] userName) {
1204    }
1205
1206    public static final String createSelectionPara(final int type) {
1207        String selection = null;
1208        switch (type) {
1209            case ContentType.INCOMING_CALL_HISTORY:
1210                selection = "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR "
1211                        + Calls.TYPE + "=" + CallLog.Calls.REJECTED_TYPE + ")";
1212                break;
1213            case ContentType.OUTGOING_CALL_HISTORY:
1214                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
1215                break;
1216            case ContentType.MISSED_CALL_HISTORY:
1217                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
1218                break;
1219            default:
1220                break;
1221        }
1222        if (V) Log.v(TAG, "Call log selection: " + selection);
1223        return selection;
1224    }
1225
1226    /**
1227     * XML encode special characters in the name field
1228     */
1229    private void xmlEncode(String name, StringBuilder result) {
1230        if (name == null) {
1231            return;
1232        }
1233
1234        final StringCharacterIterator iterator = new StringCharacterIterator(name);
1235        char character =  iterator.current();
1236        while (character != CharacterIterator.DONE ){
1237            if (character == '<') {
1238                result.append("&lt;");
1239            }
1240            else if (character == '>') {
1241                result.append("&gt;");
1242            }
1243            else if (character == '\"') {
1244                result.append("&quot;");
1245            }
1246            else if (character == '\'') {
1247                result.append("&#039;");
1248            }
1249            else if (character == '&') {
1250                result.append("&amp;");
1251            }
1252            else {
1253                // The char is not a special one, add it to the result as is
1254                result.append(character);
1255            }
1256            character = iterator.next();
1257        }
1258    }
1259
1260    private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
1261        result.append("<card handle=\"");
1262        result.append(vcfIndex);
1263        result.append(".vcf\" name=\"");
1264        xmlEncode(name, result);
1265        result.append("\"/>");
1266    }
1267
1268    private void notifyUpdateWakeLock() {
1269        Message msg = Message.obtain(mCallback);
1270        msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK;
1271        msg.sendToTarget();
1272    }
1273
1274    public static final void logHeader(HeaderSet hs) {
1275        Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1276        try {
1277
1278            Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1279            Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1280            Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1281            Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1282            Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1283            Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1284            Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1285            Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1286            Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1287            Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1288            Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1289            Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1290        } catch (IOException e) {
1291            Log.e(TAG, "dump HeaderSet error " + e);
1292        }
1293    }
1294
1295    private void setDbCounters(ApplicationParameter ap) {
1296        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.DATABASEIDENTIFIER_TAGID,
1297                ApplicationParameter.TRIPLET_LENGTH.DATABASEIDENTIFIER_LENGTH,
1298                getDatabaseIdentifier());
1299    }
1300
1301    private void setFolderVersionCounters(ApplicationParameter ap) {
1302        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1303                ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1304                getPBPrimaryFolderVersion());
1305        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1306                ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1307                getPBSecondaryFolderVersion());
1308    }
1309
1310    private void setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue) {
1311        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1312                ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1313                appParamValue.callHistoryVersionCounter);
1314
1315        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1316                ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1317                appParamValue.callHistoryVersionCounter);
1318    }
1319
1320    private byte[] getDatabaseIdentifier() {
1321        mDatabaseIdentifierHigh = 0;
1322        mDatabaseIdentifierLow = mService.getDbIdentifier();
1323        if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
1324                && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
1325            ByteBuffer ret = ByteBuffer.allocate(16);
1326            ret.putLong(mDatabaseIdentifierHigh);
1327            ret.putLong(mDatabaseIdentifierLow);
1328            return ret.array();
1329        } else {
1330            return null;
1331        }
1332    }
1333
1334    private byte[] getPBPrimaryFolderVersion() {
1335        long primaryVcMsb = 0;
1336        ByteBuffer pvc = ByteBuffer.allocate(16);
1337        pvc.putLong(primaryVcMsb);
1338
1339        Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter);
1340        pvc.putLong(BluetoothPbapUtils.sPrimaryVersionCounter);
1341        return pvc.array();
1342    }
1343
1344    private byte[] getPBSecondaryFolderVersion() {
1345        long secondaryVcMsb = 0;
1346        ByteBuffer svc = ByteBuffer.allocate(16);
1347        svc.putLong(secondaryVcMsb);
1348
1349        Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.sSecondaryVersionCounter);
1350        svc.putLong(BluetoothPbapUtils.sSecondaryVersionCounter);
1351        return svc.array();
1352    }
1353
1354    private boolean checkPbapFeatureSupport(long featureBitMask) {
1355        Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask);
1356        return ((ByteBuffer.wrap(mConnAppParamValue.supportedFeature).getInt() & featureBitMask)
1357                != 0);
1358    }
1359}
1360