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