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