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