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