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