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.ContactsContract.Contacts;
     42 import android.provider.CallLog;
     43 
     44 import java.io.IOException;
     45 import java.io.OutputStream;
     46 import java.util.ArrayList;
     47 import java.util.Arrays;
     48 
     49 import javax.obex.ServerRequestHandler;
     50 import javax.obex.ResponseCodes;
     51 import javax.obex.ApplicationParameter;
     52 import javax.obex.ServerOperation;
     53 import javax.obex.Operation;
     54 import javax.obex.HeaderSet;
     55 
     56 public class BluetoothPbapObexServer extends ServerRequestHandler {
     57 
     58     private static final String TAG = "BluetoothPbapObexServer";
     59 
     60     private static final boolean D = BluetoothPbapService.DEBUG;
     61 
     62     private static final boolean V = BluetoothPbapService.VERBOSE;
     63 
     64     private static final int UUID_LENGTH = 16;
     65 
     66     // The length of suffix of vcard name - ".vcf" is 5
     67     private static final int VCARD_NAME_SUFFIX_LENGTH = 5;
     68 
     69     // 128 bit UUID for PBAP
     70     private static final byte[] PBAP_TARGET = new byte[] {
     71             0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66,
     72             0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66
     73     };
     74 
     75     // Currently not support SIM card
     76     private static final String[] LEGAL_PATH = {
     77             "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
     78             "/telecom/cch"
     79     };
     80 
     81     @SuppressWarnings("unused")
     82     private static final String[] LEGAL_PATH_WITH_SIM = {
     83             "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
     84             "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och",
     85             "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb"
     86 
     87     };
     88 
     89     // SIM card
     90     private static final String SIM1 = "SIM1";
     91 
     92     // missed call history
     93     private static final String MCH = "mch";
     94 
     95     // incoming call history
     96     private static final String ICH = "ich";
     97 
     98     // outgoing call history
     99     private static final String OCH = "och";
    100 
    101     // combined call history
    102     private static final String CCH = "cch";
    103 
    104     // phone book
    105     private static final String PB = "pb";
    106 
    107     private static final String ICH_PATH = "/telecom/ich";
    108 
    109     private static final String OCH_PATH = "/telecom/och";
    110 
    111     private static final String MCH_PATH = "/telecom/mch";
    112 
    113     private static final String CCH_PATH = "/telecom/cch";
    114 
    115     private static final String PB_PATH = "/telecom/pb";
    116 
    117     // type for list vcard objects
    118     private static final String TYPE_LISTING = "x-bt/vcard-listing";
    119 
    120     // type for get single vcard object
    121     private static final String TYPE_VCARD = "x-bt/vcard";
    122 
    123     // to indicate if need send body besides headers
    124     private static final int NEED_SEND_BODY = -1;
    125 
    126     // type for download all vcard objects
    127     private static final String TYPE_PB = "x-bt/phonebook";
    128 
    129     // The number of indexes in the phone book.
    130     private boolean mNeedPhonebookSize = false;
    131 
    132     // The number of missed calls that have not been checked on the PSE at the
    133     // point of the request. Only apply to "mch" case.
    134     private boolean mNeedNewMissedCallsNum = false;
    135 
    136     private int mMissedCallSize = 0;
    137 
    138     // record current path the client are browsing
    139     private String mCurrentPath = "";
    140 
    141     private long mConnectionId;
    142 
    143     private Handler mCallback = null;
    144 
    145     private Context mContext;
    146 
    147     private BluetoothPbapVcardManager mVcardManager;
    148 
    149     private int mOrderBy  = ORDER_BY_INDEXED;
    150 
    151     public static int ORDER_BY_INDEXED = 0;
    152 
    153     public static int ORDER_BY_ALPHABETICAL = 1;
    154 
    155     public static boolean sIsAborted = false;
    156 
    157     public static class ContentType {
    158         public static final int PHONEBOOK = 1;
    159 
    160         public static final int INCOMING_CALL_HISTORY = 2;
    161 
    162         public static final int OUTGOING_CALL_HISTORY = 3;
    163 
    164         public static final int MISSED_CALL_HISTORY = 4;
    165 
    166         public static final int COMBINED_CALL_HISTORY = 5;
    167     }
    168 
    169     public BluetoothPbapObexServer(Handler callback, Context context) {
    170         super();
    171         mConnectionId = -1;
    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 (!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 = 0;
    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                     for (int k = 1; k <= appParam[i]; k++) {
    490                         appParamValue.searchValue += Byte.toString(appParam[i + k]);
    491                     }
    492                     // length of search value is variable
    493                     i += appParam[i];
    494                     i += 1;
    495                     break;
    496                 case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
    497                     i += 2;
    498                     appParamValue.searchAttr = Byte.toString(appParam[i]);
    499                     i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
    500                     break;
    501                 case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
    502                     i += 2;
    503                     if (appParam[i] == 0 && appParam[i + 1] == 0) {
    504                         mNeedPhonebookSize = true;
    505                     } else {
    506                         int highValue = appParam[i] & 0xff;
    507                         int lowValue = appParam[i + 1] & 0xff;
    508                         appParamValue.maxListCount = highValue * 256 + lowValue;
    509                     }
    510                     i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
    511                     break;
    512                 case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
    513                     i += 2;
    514                     int highValue = appParam[i] & 0xff;
    515                     int lowValue = appParam[i + 1] & 0xff;
    516                     appParamValue.listStartOffset = highValue * 256 + lowValue;
    517                     i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
    518                     break;
    519                 case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
    520                     i += 2;// length field in triplet
    521                     if (appParam[i] != 0) {
    522                         appParamValue.vcard21 = false;
    523                     }
    524                     i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
    525                     break;
    526                 default:
    527                     parseOk = false;
    528                     Log.e(TAG, "Parse Application Parameter error");
    529                     break;
    530             }
    531         }
    532 
    533         if (D) appParamValue.dump();
    534 
    535         return parseOk;
    536     }
    537 
    538     /** Form and Send an XML format String to client for Phone book listing */
    539     private final int sendVcardListingXml(final int type, Operation op,
    540             final int maxListCount, final int listStartOffset, final String searchValue,
    541             String searchAttr) {
    542         StringBuilder result = new StringBuilder();
    543         int itemsFound = 0;
    544         result.append("<?xml version=\"1.0\"?>");
    545         result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
    546         result.append("<vCard-listing version=\"1.0\">");
    547 
    548         // Phonebook listing request
    549         if (type == ContentType.PHONEBOOK) {
    550             // begin of search by name
    551             if (searchAttr.equals("0")) {
    552                 ArrayList<String> nameList = mVcardManager.getPhonebookNameList(mOrderBy );
    553                 int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size();
    554                 int startPoint = listStartOffset;
    555                 int endPoint = startPoint + requestSize;
    556                 if (endPoint > nameList.size()) {
    557                     endPoint = nameList.size();
    558                 }
    559 
    560                 if (D) Log.d(TAG, "search by name, size=" + requestSize + " offset=" +
    561                         listStartOffset + " searchValue=" + searchValue);
    562 
    563 
    564                 // if searchValue if not set by client,provide the entire
    565                 // list by name
    566                 if (searchValue == null || searchValue.trim().length() == 0) {
    567                     for (int j = startPoint; j < endPoint; j++) {
    568                         result.append("<card handle=\"" + j + ".vcf\" name=\"" + nameList.get(j)
    569                                 + "\"" + "/>");
    570                         itemsFound++;
    571                     }
    572                 } else {
    573                     for (int j = startPoint; j < endPoint; j++) {
    574                         // only find the name which begins with the searchValue
    575                         if (nameList.get(j).startsWith(searchValue.trim())) {
    576                             // TODO: PCE not work with it
    577                             itemsFound++;
    578                             result.append("<card handle=\"" + j + ".vcf\" name=\""
    579                                     + nameList.get(j) + "\"" + "/>");
    580                         }
    581                     }
    582                 }
    583             }// end of search by name
    584             // begin of search by number
    585             else if (searchAttr.equals("1")) {
    586                 ArrayList<String> numberList = mVcardManager.getPhonebookNumberList();
    587                 int requestSize = numberList.size() >= maxListCount ? maxListCount : numberList
    588                         .size();
    589                 int startPoint = listStartOffset;
    590                 int endPoint = startPoint + requestSize;
    591                 if (endPoint > numberList.size()) {
    592                     endPoint = numberList.size();
    593                 }
    594 
    595                 if (D) Log.d(TAG, "search by number, size=" + requestSize + " offset="
    596                             + listStartOffset + " searchValue=" + searchValue);
    597 
    598                 // if searchValue if not set by client,provide the entire
    599                 // list by number
    600                 if (searchValue == null || searchValue.trim().length() == 0) {
    601                     for (int j = startPoint; j < endPoint; j++) {
    602                         result.append("<card handle=\"" + j + ".vcf\" number=\""
    603                                 + numberList.get(j) + "\"" + "/>");
    604                         itemsFound++;
    605                     }
    606                 } else {
    607                     for (int j = startPoint; j < endPoint; j++) {
    608                         // only find the name which begins with the searchValue
    609                         if (numberList.get(j).startsWith(searchValue.trim())) {
    610                             itemsFound++;
    611                             result.append("<card handle=\"" + j + ".vcf\" number=\""
    612                                     + numberList.get(j) + "\"" + "/>");
    613                         }
    614                     }
    615                 }
    616             }// end of search by number
    617             else {
    618                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    619             }
    620         }
    621         // Call history listing request
    622         else {
    623             ArrayList<String> nameList = mVcardManager.loadCallHistoryList(type);
    624             int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size();
    625             int startPoint = listStartOffset;
    626             int endPoint = startPoint + requestSize;
    627             if (endPoint > nameList.size()) {
    628                 endPoint = nameList.size();
    629             }
    630             if (D) Log.d(TAG, "call log list, size=" + requestSize + " offset=" + listStartOffset);
    631 
    632             for (int j = startPoint; j < endPoint; j++) {
    633                 // listing object begin with 1.vcf
    634                 result.append("<card handle=\"" + (j + 1) + ".vcf\" name=\"" + nameList.get(j)
    635                         + "\"" + "/>");
    636                 itemsFound++;
    637             }
    638         }
    639         result.append("</vCard-listing>");
    640 
    641         if (V) Log.v(TAG, "itemsFound =" + itemsFound);
    642 
    643         return pushBytes(op, result.toString());
    644     }
    645 
    646     /**
    647      * Function to send obex header back to client such as get phonebook size
    648      * request
    649      */
    650     private final int pushHeader(final Operation op, final HeaderSet reply) {
    651         OutputStream outputStream = null;
    652 
    653         if (D) Log.d(TAG, "Push Header");
    654         if (D) Log.d(TAG, reply.toString());
    655 
    656         int pushResult = ResponseCodes.OBEX_HTTP_OK;
    657         try {
    658             op.sendHeaders(reply);
    659             outputStream = op.openOutputStream();
    660             outputStream.flush();
    661         } catch (IOException e) {
    662             Log.e(TAG, e.toString());
    663             pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    664         } finally {
    665             if (!closeStream(outputStream, op)) {
    666                 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    667             }
    668         }
    669         return pushResult;
    670     }
    671 
    672     /** Function to send vcard data to client */
    673     private final int pushBytes(Operation op, final String vcardString) {
    674         if (vcardString == null) {
    675             Log.w(TAG, "vcardString is null!");
    676             return ResponseCodes.OBEX_HTTP_OK;
    677         }
    678 
    679         int vcardStringLen = vcardString.length();
    680         if (D) Log.d(TAG, "Send Data: len=" + vcardStringLen);
    681 
    682         OutputStream outputStream = null;
    683         int pushResult = ResponseCodes.OBEX_HTTP_OK;
    684         try {
    685             outputStream = op.openOutputStream();
    686         } catch (IOException e) {
    687             Log.e(TAG, "open outputstrem failed" + e.toString());
    688             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    689         }
    690 
    691         int position = 0;
    692         long timestamp = 0;
    693         int outputBufferSize = op.getMaxPacketSize();
    694         if (V) Log.v(TAG, "outputBufferSize = " + outputBufferSize);
    695         while (position != vcardStringLen) {
    696             if (sIsAborted) {
    697                 ((ServerOperation)op).isAborted = true;
    698                 sIsAborted = false;
    699                 break;
    700             }
    701             if (V) timestamp = System.currentTimeMillis();
    702             int readLength = outputBufferSize;
    703             if (vcardStringLen - position < outputBufferSize) {
    704                 readLength = vcardStringLen - position;
    705             }
    706             String subStr = vcardString.substring(position, position + readLength);
    707             try {
    708                 outputStream.write(subStr.getBytes(), 0, readLength);
    709             } catch (IOException e) {
    710                 Log.e(TAG, "write outputstrem failed" + e.toString());
    711                 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    712                 break;
    713             }
    714             if (V) {
    715                 Log.v(TAG, "Sending vcard String position = " + position + " readLength "
    716                         + readLength + " bytes took " + (System.currentTimeMillis() - timestamp)
    717                         + " ms");
    718             }
    719             position += readLength;
    720         }
    721 
    722         if (V) Log.v(TAG, "Send Data complete!");
    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                 int nmnum = size - mMissedCallSize;
    751                 mMissedCallSize = size;
    752 
    753                 nmnum = nmnum > 0 ? nmnum : 0;
    754                 misnum[0] = (byte)nmnum;
    755                 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
    756                         ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
    757                 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
    758                             + nmnum);
    759             }
    760             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
    761 
    762             if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
    763 
    764             return pushHeader(op, reply);
    765         }
    766 
    767         // Only apply to "mch" download/listing.
    768         // NewMissedCalls is used only in the response, together with Body
    769         // header.
    770         if (mNeedNewMissedCallsNum) {
    771             if (V) Log.v(TAG, "Need new missed call num in response header.");
    772             mNeedNewMissedCallsNum = false;
    773 
    774             int nmnum = size - mMissedCallSize;
    775             mMissedCallSize = size;
    776 
    777             nmnum = nmnum > 0 ? nmnum : 0;
    778             misnum[0] = (byte)nmnum;
    779             ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
    780                     ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
    781             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
    782             if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
    783                         + nmnum);
    784 
    785             // Only Specifies the headers, not write for now, will write to PCE
    786             // together with Body
    787             try {
    788                 op.sendHeaders(reply);
    789             } catch (IOException e) {
    790                 Log.e(TAG, e.toString());
    791                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    792             }
    793         }
    794         return NEED_SEND_BODY;
    795     }
    796 
    797     private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
    798             HeaderSet reply, Operation op) {
    799         String searchAttr = appParamValue.searchAttr.trim();
    800 
    801         if (searchAttr == null || searchAttr.length() == 0) {
    802             // If searchAttr is not set by PCE, set default value per spec.
    803             appParamValue.searchAttr = "0";
    804             if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
    805         } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
    806             Log.w(TAG, "search attr not supported");
    807             if (searchAttr.equals("2")) {
    808                 // search by sound is not supported currently
    809                 Log.w(TAG, "do not support search by sound");
    810                 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    811             }
    812             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    813         } else {
    814             Log.i(TAG, "searchAttr is valid: " + searchAttr);
    815         }
    816 
    817         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
    818         int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op);
    819         if (needSendBody != NEED_SEND_BODY) {
    820             return needSendBody;
    821         }
    822 
    823         if (size == 0) {
    824             if (V) Log.v(TAG, "PhonebookSize is 0, return.");
    825             return ResponseCodes.OBEX_HTTP_OK;
    826         }
    827 
    828         String orderPara = appParamValue.order.trim();
    829         if (TextUtils.isEmpty(orderPara)) {
    830             // If order parameter is not set by PCE, set default value per spec.
    831             appParamValue.order = "0";
    832             if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
    833                        "Assume order by 'Indexed' by default");
    834         } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
    835             if (V) Log.v(TAG, "Order parameter is not supported: " + appParamValue.order);
    836             if (orderPara.equals("2")) {
    837                 // Order by sound is not supported currently
    838                 Log.w(TAG, "Do not support order by sound");
    839                 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    840             }
    841             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    842         } else {
    843             Log.i(TAG, "Order parameter is valid: " + orderPara);
    844         }
    845 
    846         if (orderPara.equals("0")) {
    847             mOrderBy = ORDER_BY_INDEXED;
    848         } else if (orderPara.equals("1")) {
    849             mOrderBy = ORDER_BY_ALPHABETICAL;
    850         }
    851 
    852         int sendResult = sendVcardListingXml(appParamValue.needTag, op, appParamValue.maxListCount,
    853                 appParamValue.listStartOffset, appParamValue.searchValue,
    854                 appParamValue.searchAttr);
    855         return sendResult;
    856     }
    857 
    858     private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue,
    859             Operation op, final String name, final String current_path) {
    860         if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
    861             if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
    862             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    863         }
    864         String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
    865         int intIndex = 0;
    866         if (strIndex.trim().length() != 0) {
    867             try {
    868                 intIndex = Integer.parseInt(strIndex);
    869             } catch (NumberFormatException e) {
    870                 Log.e(TAG, "catch number format exception " + e.toString());
    871                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    872             }
    873         }
    874 
    875         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
    876         if (size == 0) {
    877             if (V) Log.v(TAG, "PhonebookSize is 0, return.");
    878             return ResponseCodes.OBEX_HTTP_OK;
    879         }
    880 
    881         boolean vcard21 = appParamValue.vcard21;
    882         if (appParamValue.needTag == 0) {
    883             Log.w(TAG, "wrong path!");
    884             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    885         } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
    886             if (intIndex < 0 || intIndex >= size) {
    887                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
    888                 return ResponseCodes.OBEX_HTTP_OK;
    889             } else if (intIndex == 0) {
    890                 // For PB_PATH, 0.vcf is the phone number of this phone.
    891                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21);
    892                 return pushBytes(op, ownerVcard);
    893             } else {
    894                 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
    895                         mOrderBy );
    896             }
    897         } else {
    898             if (intIndex <= 0 || intIndex > size) {
    899                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
    900                 return ResponseCodes.OBEX_HTTP_OK;
    901             }
    902             // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
    903             // begin from 1.vcf
    904             if (intIndex >= 1) {
    905                 return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
    906                         intIndex, intIndex, vcard21);
    907             }
    908         }
    909         return ResponseCodes.OBEX_HTTP_OK;
    910     }
    911 
    912     private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
    913             Operation op, final String name) {
    914         // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
    915         if (name != null) {
    916             int dotIndex = name.indexOf(".");
    917             String vcf = "vcf";
    918             if (dotIndex >= 0 && dotIndex <= name.length()) {
    919                 if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
    920                     Log.w(TAG, "name is not .vcf");
    921                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    922                 }
    923             }
    924         } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
    925 
    926         int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
    927         int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op);
    928         if (needSendBody != NEED_SEND_BODY) {
    929             return needSendBody;
    930         }
    931 
    932         if (pbSize == 0) {
    933             if (V) Log.v(TAG, "PhonebookSize is 0, return.");
    934             return ResponseCodes.OBEX_HTTP_OK;
    935         }
    936 
    937         int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
    938                 : pbSize;
    939         int startPoint = appParamValue.listStartOffset;
    940         if (startPoint < 0 || startPoint >= pbSize) {
    941             Log.w(TAG, "listStartOffset is not correct! " + startPoint);
    942             return ResponseCodes.OBEX_HTTP_OK;
    943         }
    944 
    945         int endPoint = startPoint + requestSize - 1;
    946         if (endPoint > pbSize - 1) {
    947             endPoint = pbSize - 1;
    948         }
    949         if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
    950                 startPoint + " endPoint=" + endPoint);
    951 
    952         String result = null;
    953         boolean vcard21 = appParamValue.vcard21;
    954         if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
    955             if (startPoint == 0) {
    956                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21);
    957                 if (endPoint == 0) {
    958                     return pushBytes(op, ownerVcard);
    959                 } else {
    960                     return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
    961                             ownerVcard);
    962                 }
    963             } else {
    964                 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
    965                         vcard21, null);
    966             }
    967         } else {
    968             return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
    969                     startPoint + 1, endPoint + 1, vcard21);
    970         }
    971     }
    972 
    973     public static boolean closeStream(final OutputStream out, final Operation op) {
    974         boolean returnvalue = true;
    975         try {
    976             if (out != null) {
    977                 out.close();
    978             }
    979         } catch (IOException e) {
    980             Log.e(TAG, "outputStream close failed" + e.toString());
    981             returnvalue = false;
    982         }
    983         try {
    984             if (op != null) {
    985                 op.close();
    986             }
    987         } catch (IOException e) {
    988             Log.e(TAG, "operation close failed" + e.toString());
    989             returnvalue = false;
    990         }
    991         return returnvalue;
    992     }
    993 
    994     // Reserved for future use. In case PSE challenge PCE and PCE input wrong
    995     // session key.
    996     public final void onAuthenticationFailure(final byte[] userName) {
    997     }
    998 
    999     public static final String createSelectionPara(final int type) {
   1000         String selection = null;
   1001         switch (type) {
   1002             case ContentType.INCOMING_CALL_HISTORY:
   1003                 selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
   1004                 break;
   1005             case ContentType.OUTGOING_CALL_HISTORY:
   1006                 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
   1007                 break;
   1008             case ContentType.MISSED_CALL_HISTORY:
   1009                 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
   1010                 break;
   1011             default:
   1012                 break;
   1013         }
   1014         if (V) Log.v(TAG, "Call log selection: " + selection);
   1015         return selection;
   1016     }
   1017 
   1018     public static final void logHeader(HeaderSet hs) {
   1019         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
   1020         try {
   1021 
   1022             Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
   1023             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
   1024             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
   1025             Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
   1026             Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
   1027             Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
   1028             Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
   1029             Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
   1030             Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
   1031             Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
   1032             Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
   1033             Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
   1034         } catch (IOException e) {
   1035             Log.e(TAG, "dump HeaderSet error " + e);
   1036         }
   1037     }
   1038 }
   1039