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