BluetoothPbapObexServer.java revision 91e595b5bef1b092975992e529b2c581eb1be08b
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        String contentTypeName = name;
482        if (contentTypeName.endsWith(".vcf")) {
483            contentTypeName = contentTypeName
484                    .substring(0, contentTypeName.length() - ".vcf".length());
485        }
486        // There is a test case: Client will send a wrong name "/telecom/pbpb".
487        // So we must use the String between '/' and '/' as a indivisible part
488        // for comparing.
489        String[] nameList = contentTypeName.split("/");
490        for (String subName : nameList) {
491            if (subName.equals(target)) {
492                return true;
493            }
494        }
495        return false;
496    }
497
498    /** check whether path is legal */
499    private final boolean isLegalPath(final String str) {
500        if (str.length() == 0) {
501            return true;
502        }
503        for (int i = 0; i < LEGAL_PATH.length; i++) {
504            if (str.equals(LEGAL_PATH[i])) {
505                return true;
506            }
507        }
508        return false;
509    }
510
511    private class AppParamValue {
512        public int maxListCount;
513
514        public int listStartOffset;
515
516        public String searchValue;
517
518        // Indicate which vCard parameter the search operation shall be carried
519        // out on. Can be "Name | Number | Sound", default value is "Name".
520        public String searchAttr;
521
522        // Indicate which sorting order shall be used for the
523        // <x-bt/vcard-listing> listing object.
524        // Can be "Alphabetical | Indexed | Phonetical", default value is
525        // "Indexed".
526        public String order;
527
528        public int needTag;
529
530        public boolean vcard21;
531
532        public byte[] propertySelector;
533
534        public byte[] supportedFeature;
535
536        public boolean ignorefilter;
537
538        public byte[] vCardSelector;
539
540        public String vCardSelectorOperator;
541
542        public byte[] callHistoryVersionCounter;
543
544        public AppParamValue() {
545            maxListCount = 0xFFFF;
546            listStartOffset = 0;
547            searchValue = "";
548            searchAttr = "";
549            order = "";
550            needTag = 0x00;
551            vcard21 = true;
552            //Filter is not set by default
553            ignorefilter = true;
554            vCardSelectorOperator = "0";
555            propertySelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
556            vCardSelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
557            supportedFeature = new byte[] {0x00, 0x00, 0x00, 0x00};
558        }
559
560        public void dump() {
561            Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
562                            + " searchValue=" + searchValue + " searchAttr=" + searchAttr
563                            + " needTag=" + needTag + " vcard21=" + vcard21 + " order=" + order
564                            + "vcardselector=" + vCardSelector + "vcardselop="
565                            + vCardSelectorOperator);
566        }
567    }
568
569    /** To parse obex application parameter */
570    private final boolean parseApplicationParameter(final byte[] appParam,
571            AppParamValue appParamValue) {
572        int i = 0;
573        boolean parseOk = true;
574        while ((i < appParam.length) && (parseOk == true)) {
575            switch (appParam[i]) {
576                case ApplicationParameter.TRIPLET_TAGID.PROPERTY_SELECTOR_TAGID:
577                    i += 2; // length and tag field in triplet
578                    for (int index = 0;
579                            index < ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH;
580                            index++) {
581                        if (appParam[i+index] != 0){
582                            appParamValue.ignorefilter = false;
583                            appParamValue.propertySelector[index] = appParam[i + index];
584                        }
585                    }
586                    i += ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH;
587                    break;
588                case ApplicationParameter.TRIPLET_TAGID.SUPPORTEDFEATURE_TAGID:
589                    i += 2; // length and tag field in triplet
590                    for (int index = 0;
591                            index < ApplicationParameter.TRIPLET_LENGTH.SUPPORTEDFEATURE_LENGTH;
592                            index++) {
593                        if (appParam[i + index] != 0) {
594                            appParamValue.supportedFeature[index] = appParam[i + index];
595                        }
596                    }
597
598                    i += ApplicationParameter.TRIPLET_LENGTH.SUPPORTEDFEATURE_LENGTH;
599                    break;
600
601                case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID:
602                    i += 2; // length and tag field in triplet
603                    appParamValue.order = Byte.toString(appParam[i]);
604                    i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH;
605                    break;
606                case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID:
607                    i += 1; // length field in triplet
608                    // length of search value is variable
609                    int length = appParam[i];
610                    if (length == 0) {
611                        parseOk = false;
612                        break;
613                    }
614                    if (appParam[i+length] == 0x0) {
615                        appParamValue.searchValue = new String(appParam, i + 1, length-1);
616                    } else {
617                        appParamValue.searchValue = new String(appParam, i + 1, length);
618                    }
619                    i += length;
620                    i += 1;
621                    break;
622                case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
623                    i += 2;
624                    appParamValue.searchAttr = Byte.toString(appParam[i]);
625                    i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
626                    break;
627                case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
628                    i += 2;
629                    if (appParam[i] == 0 && appParam[i + 1] == 0) {
630                        mNeedPhonebookSize = true;
631                    } else {
632                        int highValue = appParam[i] & 0xff;
633                        int lowValue = appParam[i + 1] & 0xff;
634                        appParamValue.maxListCount = highValue * 256 + lowValue;
635                    }
636                    i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
637                    break;
638                case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
639                    i += 2;
640                    int highValue = appParam[i] & 0xff;
641                    int lowValue = appParam[i + 1] & 0xff;
642                    appParamValue.listStartOffset = highValue * 256 + lowValue;
643                    i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
644                    break;
645                case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
646                    i += 2;// length field in triplet
647                    if (appParam[i] != 0) {
648                        appParamValue.vcard21 = false;
649                    }
650                    i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
651                    break;
652
653                case ApplicationParameter.TRIPLET_TAGID.VCARDSELECTOR_TAGID:
654                    i += 2;
655                    for (int index = 0;
656                            index < ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOR_LENGTH;
657                            index++) {
658                        if (appParam[i + index] != 0) {
659                            mVcardSelector = true;
660                            appParamValue.vCardSelector[index] = appParam[i + index];
661                        }
662                    }
663                    i += ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOR_LENGTH;
664                    break;
665                case ApplicationParameter.TRIPLET_TAGID.VCARDSELECTOROPERATOR_TAGID:
666                    i += 2;
667                    appParamValue.vCardSelectorOperator = Byte.toString(appParam[i]);
668                    i += ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOROPERATOR_LENGTH;
669                    break;
670                default:
671                    parseOk = false;
672                    Log.e(TAG, "Parse Application Parameter error");
673                    break;
674            }
675        }
676
677        if (D) appParamValue.dump();
678
679        return parseOk;
680    }
681
682    /** Form and Send an XML format String to client for Phone book listing */
683    private final int sendVcardListingXml(
684            AppParamValue appParamValue, Operation op, int needSendBody, int size) {
685        StringBuilder result = new StringBuilder();
686        int itemsFound = 0;
687        result.append("<?xml version=\"1.0\"?>");
688        result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
689        result.append("<vCard-listing version=\"1.0\">");
690
691        // Phonebook listing request
692        if (appParamValue.needTag == ContentType.PHONEBOOK) {
693            String type = "";
694            if (appParamValue.searchAttr.equals("0")) {
695                type = "name";
696            } else if (appParamValue.searchAttr.equals("1")) {
697                type = "number";
698            }
699            if (type.length() > 0) {
700                itemsFound = createList(appParamValue, needSendBody, size, result, type);
701            } else {
702                return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
703            }
704        }
705        // Call history listing request
706        else {
707            ArrayList<String> nameList = mVcardManager.loadCallHistoryList(appParamValue.needTag);
708            int requestSize = nameList.size() >= appParamValue.maxListCount
709                    ? appParamValue.maxListCount
710                    : nameList.size();
711            int startPoint = appParamValue.listStartOffset;
712            int endPoint = startPoint + requestSize;
713            if (endPoint > nameList.size()) {
714                endPoint = nameList.size();
715            }
716            if (D)
717                Log.d(TAG, "call log list, size=" + requestSize + " offset="
718                                + appParamValue.listStartOffset);
719
720            for (int j = startPoint; j < endPoint; j++) {
721                writeVCardEntry(j+1, nameList.get(j),result);
722            }
723        }
724        result.append("</vCard-listing>");
725
726        if (D) Log.d(TAG, "itemsFound =" + itemsFound);
727
728        return pushBytes(op, result.toString());
729    }
730
731    private int createList(AppParamValue appParamValue, int needSendBody, int size,
732            StringBuilder result, String type) {
733        int itemsFound = 0;
734
735        ArrayList<String> nameList = null;
736        if (mVcardSelector) {
737            nameList = mVcardManager.getSelectedPhonebookNameList(mOrderBy, appParamValue.vcard21,
738                    needSendBody, size, appParamValue.vCardSelector,
739                    appParamValue.vCardSelectorOperator);
740        } else {
741            nameList = mVcardManager.getPhonebookNameList(mOrderBy);
742        }
743
744        final int requestSize = nameList.size() >= appParamValue.maxListCount
745                ? appParamValue.maxListCount
746                : nameList.size();
747        final int listSize = nameList.size();
748        String compareValue = "", currentValue;
749
750        if (D)
751            Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset="
752                            + appParamValue.listStartOffset + " searchValue="
753                            + appParamValue.searchValue);
754
755        if (type.equals("number")) {
756            // query the number, to get the names
757            ArrayList<String> names =
758                    mVcardManager.getContactNamesByNumber(appParamValue.searchValue);
759            for (int i = 0; i < names.size(); i++) {
760                compareValue = names.get(i).trim();
761                if (D) Log.d(TAG, "compareValue=" + compareValue);
762                for (int pos = appParamValue.listStartOffset;
763                        pos < listSize && itemsFound < requestSize; pos++) {
764                    currentValue = nameList.get(pos);
765                    if (D) Log.d(TAG, "currentValue=" + currentValue);
766                    if (currentValue.equals(compareValue)) {
767                        itemsFound++;
768                        if (currentValue.contains(","))
769                           currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
770                        writeVCardEntry(pos, currentValue,result);
771                    }
772                }
773                if (itemsFound >= requestSize) {
774                    break;
775                }
776            }
777        } else {
778            if (appParamValue.searchValue != null) {
779                compareValue = appParamValue.searchValue.trim().toLowerCase();
780            }
781            for (int pos = appParamValue.listStartOffset;
782                    pos < listSize && itemsFound < requestSize; pos++) {
783                currentValue = nameList.get(pos);
784                if (currentValue.contains(","))
785                    currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
786
787                if (appParamValue.searchValue.isEmpty()
788                        || ((currentValue.toLowerCase()).startsWith(compareValue))) {
789                    itemsFound++;
790                    writeVCardEntry(pos, currentValue,result);
791                }
792            }
793        }
794        return itemsFound;
795    }
796
797    /**
798     * Function to send obex header back to client such as get phonebook size
799     * request
800     */
801    private final int pushHeader(final Operation op, final HeaderSet reply) {
802        OutputStream outputStream = null;
803
804        if (D) Log.d(TAG, "Push Header");
805        if (D) Log.d(TAG, reply.toString());
806
807        int pushResult = ResponseCodes.OBEX_HTTP_OK;
808        try {
809            op.sendHeaders(reply);
810            outputStream = op.openOutputStream();
811            outputStream.flush();
812        } catch (IOException e) {
813            Log.e(TAG, e.toString());
814            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
815        } finally {
816            if (!closeStream(outputStream, op)) {
817                pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
818            }
819        }
820        return pushResult;
821    }
822
823    /** Function to send vcard data to client */
824    private final int pushBytes(Operation op, final String vcardString) {
825        if (vcardString == null) {
826            Log.w(TAG, "vcardString is null!");
827            return ResponseCodes.OBEX_HTTP_OK;
828        }
829
830        OutputStream outputStream = null;
831        int pushResult = ResponseCodes.OBEX_HTTP_OK;
832        try {
833            outputStream = op.openOutputStream();
834            outputStream.write(vcardString.getBytes());
835            if (V) Log.v(TAG, "Send Data complete!");
836        } catch (IOException e) {
837            Log.e(TAG, "open/write outputstrem failed" + e.toString());
838            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
839        }
840
841        if (!closeStream(outputStream, op)) {
842            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
843        }
844
845        return pushResult;
846    }
847
848    private final int handleAppParaForResponse(
849            AppParamValue appParamValue, int size, HeaderSet reply, Operation op, String name) {
850        byte[] misnum = new byte[1];
851        ApplicationParameter ap = new ApplicationParameter();
852        boolean needSendCallHistoryVersionCounters = false;
853        if (isNameMatchTarget(name, MCH) || isNameMatchTarget(name, ICH)
854                || isNameMatchTarget(name, OCH) || isNameMatchTarget(name, CCH))
855            needSendCallHistoryVersionCounters =
856                    checkPbapFeatureSupport(folderVersionCounterbitMask);
857        boolean needSendPhonebookVersionCounters = false;
858        if (isNameMatchTarget(name, PB))
859            needSendPhonebookVersionCounters = checkPbapFeatureSupport(folderVersionCounterbitMask);
860
861        // In such case, PCE only want the number of index.
862        // So response not contain any Body header.
863        if (mNeedPhonebookSize) {
864            if (D) Log.d(TAG, "Need Phonebook size in response header.");
865            mNeedPhonebookSize = false;
866
867            byte[] pbsize = new byte[2];
868
869            pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
870            pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
871            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
872                    ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
873
874            if (mNeedNewMissedCallsNum) {
875                mNeedNewMissedCallsNum = false;
876                int nmnum = 0;
877                ContentResolver contentResolver;
878                contentResolver = mContext.getContentResolver();
879
880                Cursor c = contentResolver.query(
881                    Calls.CONTENT_URI,
882                    null,
883                    Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1",
884                    null,
885                    Calls.DEFAULT_SORT_ORDER);
886
887                if (c != null) {
888                  nmnum = c.getCount();
889                  c.close();
890                }
891
892                nmnum = nmnum > 0 ? nmnum : 0;
893                misnum[0] = (byte)nmnum;
894                if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
895            }
896
897            if (checkPbapFeatureSupport(databaseIdentifierBitMask)) {
898                setDbCounters(ap);
899            }
900            if (needSendPhonebookVersionCounters) {
901                setFolderVersionCounters(ap);
902            }
903            if (needSendCallHistoryVersionCounters) {
904                setCallversionCounters(ap, appParamValue);
905            }
906            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
907
908            if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
909
910            return pushHeader(op, reply);
911        }
912
913        // Only apply to "mch" download/listing.
914        // NewMissedCalls is used only in the response, together with Body
915        // header.
916        if (mNeedNewMissedCallsNum) {
917            if (D) Log.d(TAG, "Need new missed call num in response header.");
918            mNeedNewMissedCallsNum = false;
919            int nmnum = 0;
920            ContentResolver contentResolver;
921            contentResolver = mContext.getContentResolver();
922
923            Cursor c = contentResolver.query(
924                Calls.CONTENT_URI,
925                null,
926                Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1",
927                null,
928                Calls.DEFAULT_SORT_ORDER);
929
930            if (c != null) {
931              nmnum = c.getCount();
932              c.close();
933            }
934
935            nmnum = nmnum > 0 ? nmnum : 0;
936            misnum[0] = (byte)nmnum;
937            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
938
939            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
940                    ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
941            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
942            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
943                        + nmnum);
944
945            // Only Specifies the headers, not write for now, will write to PCE
946            // together with Body
947            try {
948                op.sendHeaders(reply);
949            } catch (IOException e) {
950                Log.e(TAG, e.toString());
951                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
952            }
953        }
954
955        if (checkPbapFeatureSupport(databaseIdentifierBitMask)) {
956            setDbCounters(ap);
957            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
958            try {
959                op.sendHeaders(reply);
960            } catch (IOException e) {
961                Log.e(TAG, e.toString());
962                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
963            }
964        }
965
966        if (needSendPhonebookVersionCounters) {
967            setFolderVersionCounters(ap);
968            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
969            try {
970                op.sendHeaders(reply);
971            } catch (IOException e) {
972                Log.e(TAG, e.toString());
973                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
974            }
975        }
976
977        if (needSendCallHistoryVersionCounters) {
978            setCallversionCounters(ap, appParamValue);
979            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
980            try {
981                op.sendHeaders(reply);
982            } catch (IOException e) {
983                Log.e(TAG, e.toString());
984                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
985            }
986        }
987
988        return NEED_SEND_BODY;
989    }
990
991    private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
992            HeaderSet reply, Operation op, String name) {
993        String searchAttr = appParamValue.searchAttr.trim();
994
995        if (searchAttr == null || searchAttr.length() == 0) {
996            // If searchAttr is not set by PCE, set default value per spec.
997            appParamValue.searchAttr = "0";
998            if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
999        } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
1000            Log.w(TAG, "search attr not supported");
1001            if (searchAttr.equals("2")) {
1002                // search by sound is not supported currently
1003                Log.w(TAG, "do not support search by sound");
1004                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1005            }
1006            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1007        } else {
1008            Log.i(TAG, "searchAttr is valid: " + searchAttr);
1009        }
1010
1011        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1012        int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1013        if (needSendBody != NEED_SEND_BODY) {
1014            op.noBodyHeader();
1015            return needSendBody;
1016        }
1017
1018        if (size == 0) {
1019            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1020            return ResponseCodes.OBEX_HTTP_OK;
1021        }
1022
1023        String orderPara = appParamValue.order.trim();
1024        if (TextUtils.isEmpty(orderPara)) {
1025            // If order parameter is not set by PCE, set default value per spec.
1026            orderPara = "0";
1027            if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
1028                       "Assume order by 'Indexed' by default");
1029        } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
1030            if (D) Log.d(TAG, "Order parameter is not supported: " + appParamValue.order);
1031            if (orderPara.equals("2")) {
1032                // Order by sound is not supported currently
1033                Log.w(TAG, "Do not support order by sound");
1034                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1035            }
1036            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1037        } else {
1038            Log.i(TAG, "Order parameter is valid: " + orderPara);
1039        }
1040
1041        if (orderPara.equals("0")) {
1042            mOrderBy = ORDER_BY_INDEXED;
1043        } else if (orderPara.equals("1")) {
1044            mOrderBy = ORDER_BY_ALPHABETICAL;
1045        }
1046
1047        return sendVcardListingXml(appParamValue, op, needSendBody, size);
1048    }
1049
1050    private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op,
1051            HeaderSet reply, final String name, final String current_path) {
1052        if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
1053            if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
1054            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1055        }
1056        String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
1057        int intIndex = 0;
1058        if (strIndex.trim().length() != 0) {
1059            try {
1060                intIndex = Integer.parseInt(strIndex);
1061            } catch (NumberFormatException e) {
1062                Log.e(TAG, "catch number format exception " + e.toString());
1063                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1064            }
1065        }
1066
1067        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1068        int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1069        if (size == 0) {
1070            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1071            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1072        }
1073
1074        boolean vcard21 = appParamValue.vcard21;
1075        if (appParamValue.needTag == 0) {
1076            Log.w(TAG, "wrong path!");
1077            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1078        } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
1079            if (intIndex < 0 || intIndex >= size) {
1080                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1081                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1082            } else if (intIndex == 0) {
1083                // For PB_PATH, 0.vcf is the phone number of this phone.
1084                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null);
1085                return pushBytes(op, ownerVcard);
1086            } else {
1087                return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
1088                        mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector);
1089            }
1090        } else {
1091            if (intIndex <= 0 || intIndex > size) {
1092                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1093                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1094            }
1095            // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
1096            // begin from 1.vcf
1097            if (intIndex >= 1) {
1098                return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1099                        intIndex, intIndex, vcard21, needSendBody, size, appParamValue.ignorefilter,
1100                        appParamValue.propertySelector, appParamValue.vCardSelector,
1101                        appParamValue.vCardSelectorOperator, mVcardSelector);
1102            }
1103        }
1104        return ResponseCodes.OBEX_HTTP_OK;
1105    }
1106
1107    private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
1108            Operation op, final String name) {
1109        // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
1110        if (name != null) {
1111            int dotIndex = name.indexOf(".");
1112            String vcf = "vcf";
1113            if (dotIndex >= 0 && dotIndex <= name.length()) {
1114                if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
1115                    Log.w(TAG, "name is not .vcf");
1116                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1117                }
1118            }
1119        } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
1120
1121        int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
1122        int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name);
1123        if (needSendBody != NEED_SEND_BODY) {
1124            op.noBodyHeader();
1125            return needSendBody;
1126        }
1127
1128        if (pbSize == 0) {
1129            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1130            return ResponseCodes.OBEX_HTTP_OK;
1131        }
1132
1133        int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
1134                : pbSize;
1135        int startPoint = appParamValue.listStartOffset;
1136        if (startPoint < 0 || startPoint >= pbSize) {
1137            Log.w(TAG, "listStartOffset is not correct! " + startPoint);
1138            return ResponseCodes.OBEX_HTTP_OK;
1139        }
1140
1141        // Limit the number of call log to CALLLOG_NUM_LIMIT
1142        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1143            if (requestSize > CALLLOG_NUM_LIMIT) {
1144               requestSize = CALLLOG_NUM_LIMIT;
1145            }
1146        }
1147
1148        int endPoint = startPoint + requestSize - 1;
1149        if (endPoint > pbSize - 1) {
1150            endPoint = pbSize - 1;
1151        }
1152        if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
1153                startPoint + " endPoint=" + endPoint);
1154
1155        boolean vcard21 = appParamValue.vcard21;
1156        if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1157            if (startPoint == 0) {
1158                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null);
1159                if (endPoint == 0) {
1160                    return pushBytes(op, ownerVcard);
1161                } else {
1162                    return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
1163                            ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter,
1164                            appParamValue.propertySelector, appParamValue.vCardSelector,
1165                            appParamValue.vCardSelectorOperator, mVcardSelector);
1166                }
1167            } else {
1168                return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
1169                        vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter,
1170                        appParamValue.propertySelector, appParamValue.vCardSelector,
1171                        appParamValue.vCardSelectorOperator, mVcardSelector);
1172            }
1173        } else {
1174            return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1175                    startPoint + 1, endPoint + 1, vcard21, needSendBody, pbSize,
1176                    appParamValue.ignorefilter, appParamValue.propertySelector,
1177                    appParamValue.vCardSelector, appParamValue.vCardSelectorOperator,
1178                    mVcardSelector);
1179        }
1180    }
1181
1182    public static boolean closeStream(final OutputStream out, final Operation op) {
1183        boolean returnvalue = true;
1184        try {
1185            if (out != null) {
1186                out.close();
1187            }
1188        } catch (IOException e) {
1189            Log.e(TAG, "outputStream close failed" + e.toString());
1190            returnvalue = false;
1191        }
1192        try {
1193            if (op != null) {
1194                op.close();
1195            }
1196        } catch (IOException e) {
1197            Log.e(TAG, "operation close failed" + e.toString());
1198            returnvalue = false;
1199        }
1200        return returnvalue;
1201    }
1202
1203    // Reserved for future use. In case PSE challenge PCE and PCE input wrong
1204    // session key.
1205    public final void onAuthenticationFailure(final byte[] userName) {
1206    }
1207
1208    public static final String createSelectionPara(final int type) {
1209        String selection = null;
1210        switch (type) {
1211            case ContentType.INCOMING_CALL_HISTORY:
1212                selection = "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR "
1213                        + Calls.TYPE + "=" + CallLog.Calls.REJECTED_TYPE + ")";
1214                break;
1215            case ContentType.OUTGOING_CALL_HISTORY:
1216                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
1217                break;
1218            case ContentType.MISSED_CALL_HISTORY:
1219                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
1220                break;
1221            default:
1222                break;
1223        }
1224        if (V) Log.v(TAG, "Call log selection: " + selection);
1225        return selection;
1226    }
1227
1228    /**
1229     * XML encode special characters in the name field
1230     */
1231    private void xmlEncode(String name, StringBuilder result) {
1232        if (name == null) {
1233            return;
1234        }
1235
1236        final StringCharacterIterator iterator = new StringCharacterIterator(name);
1237        char character =  iterator.current();
1238        while (character != CharacterIterator.DONE ){
1239            if (character == '<') {
1240                result.append("&lt;");
1241            }
1242            else if (character == '>') {
1243                result.append("&gt;");
1244            }
1245            else if (character == '\"') {
1246                result.append("&quot;");
1247            }
1248            else if (character == '\'') {
1249                result.append("&#039;");
1250            }
1251            else if (character == '&') {
1252                result.append("&amp;");
1253            }
1254            else {
1255                // The char is not a special one, add it to the result as is
1256                result.append(character);
1257            }
1258            character = iterator.next();
1259        }
1260    }
1261
1262    private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
1263        result.append("<card handle=\"");
1264        result.append(vcfIndex);
1265        result.append(".vcf\" name=\"");
1266        xmlEncode(name, result);
1267        result.append("\"/>");
1268    }
1269
1270    private void notifyUpdateWakeLock() {
1271        Message msg = Message.obtain(mCallback);
1272        msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK;
1273        msg.sendToTarget();
1274    }
1275
1276    public static final void logHeader(HeaderSet hs) {
1277        Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1278        try {
1279
1280            Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1281            Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1282            Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1283            Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1284            Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1285            Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1286            Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1287            Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1288            Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1289            Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1290            Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1291            Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1292        } catch (IOException e) {
1293            Log.e(TAG, "dump HeaderSet error " + e);
1294        }
1295    }
1296
1297    private void setDbCounters(ApplicationParameter ap) {
1298        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.DATABASEIDENTIFIER_TAGID,
1299                ApplicationParameter.TRIPLET_LENGTH.DATABASEIDENTIFIER_LENGTH,
1300                getDatabaseIdentifier());
1301    }
1302
1303    private void setFolderVersionCounters(ApplicationParameter ap) {
1304        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1305                ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1306                getPBPrimaryFolderVersion());
1307        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1308                ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1309                getPBSecondaryFolderVersion());
1310    }
1311
1312    private void setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue) {
1313        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1314                ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1315                appParamValue.callHistoryVersionCounter);
1316
1317        ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1318                ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1319                appParamValue.callHistoryVersionCounter);
1320    }
1321
1322    private byte[] getDatabaseIdentifier() {
1323        mDatabaseIdentifierHigh = 0;
1324        mDatabaseIdentifierLow = mService.getDbIdentifier();
1325        if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
1326                && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
1327            ByteBuffer ret = ByteBuffer.allocate(16);
1328            ret.putLong(mDatabaseIdentifierHigh);
1329            ret.putLong(mDatabaseIdentifierLow);
1330            return ret.array();
1331        } else {
1332            return null;
1333        }
1334    }
1335
1336    private byte[] getPBPrimaryFolderVersion() {
1337        long primaryVcMsb = 0;
1338        ByteBuffer pvc = ByteBuffer.allocate(16);
1339        pvc.putLong(primaryVcMsb);
1340
1341        Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.primaryVersionCounter);
1342        pvc.putLong(BluetoothPbapUtils.primaryVersionCounter);
1343        return pvc.array();
1344    }
1345
1346    private byte[] getPBSecondaryFolderVersion() {
1347        long secondaryVcMsb = 0;
1348        ByteBuffer svc = ByteBuffer.allocate(16);
1349        svc.putLong(secondaryVcMsb);
1350
1351        Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.secondaryVersionCounter);
1352        svc.putLong(BluetoothPbapUtils.secondaryVersionCounter);
1353        return svc.array();
1354    }
1355
1356    private boolean checkPbapFeatureSupport(long featureBitMask) {
1357        Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask);
1358        return ((ByteBuffer.wrap(connAppParamValue.supportedFeature).getInt() & featureBitMask)
1359                != 0);
1360    }
1361}
1362