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