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