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