• 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.content.ContentResolver;
37 import android.database.Cursor;
38 import android.os.Message;
39 import android.os.Handler;
40 import android.provider.CallLog;
41 import android.provider.CallLog.Calls;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.os.SystemProperties;
45 
46 import java.io.IOException;
47 import java.io.OutputStream;
48 import java.text.CharacterIterator;
49 import java.text.StringCharacterIterator;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.nio.ByteBuffer;
53 
54 import javax.obex.ServerRequestHandler;
55 import javax.obex.ResponseCodes;
56 import javax.obex.ApplicationParameter;
57 import javax.obex.Operation;
58 import javax.obex.HeaderSet;
59 
60 public class BluetoothPbapObexServer extends ServerRequestHandler {
61 
62     private static final String TAG = "BluetoothPbapObexServer";
63 
64     private static final boolean D = BluetoothPbapService.DEBUG;
65 
66     private static final boolean V = BluetoothPbapService.VERBOSE;
67 
68     private static final int UUID_LENGTH = 16;
69 
70     public static final long INVALID_VALUE_PARAMETER = -1;
71 
72     // The length of suffix of vcard name - ".vcf" is 5
73     private static final int VCARD_NAME_SUFFIX_LENGTH = 5;
74 
75     // 128 bit UUID for PBAP
76     private static final byte[] PBAP_TARGET = new byte[] {
77             0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66,
78             0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66
79     };
80 
81     // Currently not support SIM card
82     private static final String[] LEGAL_PATH = {
83             "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
84             "/telecom/cch"
85     };
86 
87     @SuppressWarnings("unused")
88     private static final String[] LEGAL_PATH_WITH_SIM = {
89             "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
90             "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och",
91             "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb"
92 
93     };
94 
95     // SIM card
96     private static final String SIM1 = "SIM1";
97 
98     // missed call history
99     private static final String MCH = "mch";
100 
101     // incoming call history
102     private static final String ICH = "ich";
103 
104     // outgoing call history
105     private static final String OCH = "och";
106 
107     // combined call history
108     private static final String CCH = "cch";
109 
110     // phone book
111     private static final String PB = "pb";
112 
113     private static final String TELECOM_PATH = "/telecom";
114 
115     private static final String ICH_PATH = "/telecom/ich";
116 
117     private static final String OCH_PATH = "/telecom/och";
118 
119     private static final String MCH_PATH = "/telecom/mch";
120 
121     private static final String CCH_PATH = "/telecom/cch";
122 
123     private static final String PB_PATH = "/telecom/pb";
124 
125     // type for list vcard objects
126     private static final String TYPE_LISTING = "x-bt/vcard-listing";
127 
128     // type for get single vcard object
129     private static final String TYPE_VCARD = "x-bt/vcard";
130 
131     // to indicate if need send body besides headers
132     private static final int NEED_SEND_BODY = -1;
133 
134     // type for download all vcard objects
135     private static final String TYPE_PB = "x-bt/phonebook";
136 
137     // The number of indexes in the phone book.
138     private boolean mNeedPhonebookSize = false;
139 
140     // The number of missed calls that have not been checked on the PSE at the
141     // point of the request. Only apply to "mch" case.
142     private boolean mNeedNewMissedCallsNum = false;
143 
144     private boolean mVcardSelector = false;
145 
146     private int mMissedCallSize = 0;
147 
148     // record current path the client are browsing
149     private String mCurrentPath = "";
150 
151     private Handler mCallback = null;
152 
153     private Context mContext;
154 
155     private BluetoothPbapService mService;
156 
157     private BluetoothPbapVcardManager mVcardManager;
158 
159     private int mOrderBy  = ORDER_BY_INDEXED;
160 
161     private static int CALLLOG_NUM_LIMIT = 50;
162 
163     public static int ORDER_BY_INDEXED = 0;
164 
165     public static int ORDER_BY_ALPHABETICAL = 1;
166 
167     public static boolean sIsAborted = false;
168 
169     private long mDatabaseIdentifierLow = INVALID_VALUE_PARAMETER;
170 
171     private long mDatabaseIdentifierHigh = INVALID_VALUE_PARAMETER;
172 
173     private long folderVersionCounterbitMask = 0x0008;
174 
175     private long databaseIdentifierBitMask = 0x0004;
176 
177     private AppParamValue connAppParamValue;
178 
179     public static class ContentType {
180         public static final int PHONEBOOK = 1;
181 
182         public static final int INCOMING_CALL_HISTORY = 2;
183 
184         public static final int OUTGOING_CALL_HISTORY = 3;
185 
186         public static final int MISSED_CALL_HISTORY = 4;
187 
188         public static final int COMBINED_CALL_HISTORY = 5;
189     }
190 
BluetoothPbapObexServer(Handler callback, BluetoothPbapService service)191     public BluetoothPbapObexServer(Handler callback, BluetoothPbapService service) {
192         super();
193         mCallback = callback;
194         mService = service;
195         mContext = service.getApplicationContext();
196         mVcardManager = new BluetoothPbapVcardManager(mContext);
197     }
198 
199     @Override
onConnect(final HeaderSet request, HeaderSet reply)200     public int onConnect(final HeaderSet request, HeaderSet reply) {
201         if (V) logHeader(request);
202         notifyUpdateWakeLock();
203         try {
204             byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
205             if (uuid == null) {
206                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
207             }
208             if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
209 
210             if (uuid.length != UUID_LENGTH) {
211                 Log.w(TAG, "Wrong UUID length");
212                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
213             }
214             for (int i = 0; i < UUID_LENGTH; i++) {
215                 if (uuid[i] != PBAP_TARGET[i]) {
216                     Log.w(TAG, "Wrong UUID");
217                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
218                 }
219             }
220             reply.setHeader(HeaderSet.WHO, uuid);
221         } catch (IOException e) {
222             Log.e(TAG, e.toString());
223             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
224         }
225 
226         try {
227             byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
228             if (remote != null) {
229                 if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
230                 reply.setHeader(HeaderSet.TARGET, remote);
231             }
232         } catch (IOException e) {
233             Log.e(TAG, e.toString());
234             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
235         }
236 
237         try {
238             byte[] appParam = null;
239             connAppParamValue = new AppParamValue();
240             appParam = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER);
241             if ((appParam != null) && !parseApplicationParameter(appParam, connAppParamValue)) {
242                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
243             }
244         } catch (IOException e) {
245             Log.e(TAG, e.toString());
246             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
247         }
248 
249         if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
250                 "MSG_SESSION_ESTABLISHED msg.");
251 
252         Message msg = Message.obtain(mCallback);
253         msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
254         msg.sendToTarget();
255         return ResponseCodes.OBEX_HTTP_OK;
256     }
257 
258     @Override
onDisconnect(final HeaderSet req, final HeaderSet resp)259     public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
260         if (D) Log.d(TAG, "onDisconnect(): enter");
261         if (V) logHeader(req);
262         notifyUpdateWakeLock();
263         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
264         if (mCallback != null) {
265             Message msg = Message.obtain(mCallback);
266             msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED;
267             msg.sendToTarget();
268             if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
269         }
270     }
271 
272     @Override
onAbort(HeaderSet request, HeaderSet reply)273     public int onAbort(HeaderSet request, HeaderSet reply) {
274         if (D) Log.d(TAG, "onAbort(): enter.");
275         notifyUpdateWakeLock();
276         sIsAborted = true;
277         return ResponseCodes.OBEX_HTTP_OK;
278     }
279 
280     @Override
onPut(final Operation op)281     public int onPut(final Operation op) {
282         if (D) Log.d(TAG, "onPut(): not support PUT request.");
283         notifyUpdateWakeLock();
284         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
285     }
286 
287     @Override
onDelete(final HeaderSet request, final HeaderSet reply)288     public int onDelete(final HeaderSet request, final HeaderSet reply) {
289         if (D) Log.d(TAG, "onDelete(): not support PUT request.");
290         notifyUpdateWakeLock();
291         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
292     }
293 
294     @Override
onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup, final boolean create)295     public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
296             final boolean create) {
297         if (V) logHeader(request);
298         if (D) Log.d(TAG, "before setPath, mCurrentPath ==  " + mCurrentPath);
299         notifyUpdateWakeLock();
300         String current_path_tmp = mCurrentPath;
301         String tmp_path = null;
302         try {
303             tmp_path = (String)request.getHeader(HeaderSet.NAME);
304         } catch (IOException e) {
305             Log.e(TAG, "Get name header fail");
306             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
307         }
308         if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmp_path);
309 
310         if (backup) {
311             if (current_path_tmp.length() != 0) {
312                 current_path_tmp = current_path_tmp.substring(0,
313                         current_path_tmp.lastIndexOf("/"));
314             }
315         } else {
316             if (tmp_path == null) {
317                 current_path_tmp = "";
318             } else {
319                 current_path_tmp = current_path_tmp + "/" + tmp_path;
320             }
321         }
322 
323         if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) {
324             if (create) {
325                 Log.w(TAG, "path create is forbidden!");
326                 return ResponseCodes.OBEX_HTTP_FORBIDDEN;
327             } else {
328                 Log.w(TAG, "path is not legal");
329                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
330             }
331         }
332         mCurrentPath = current_path_tmp;
333         if (V) Log.v(TAG, "after setPath, mCurrentPath ==  " + mCurrentPath);
334 
335         return ResponseCodes.OBEX_HTTP_OK;
336     }
337 
338     @Override
onClose()339     public void onClose() {
340         if (mCallback != null) {
341             Message msg = Message.obtain(mCallback);
342             msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
343             msg.sendToTarget();
344             if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
345         }
346     }
347 
348     @Override
onGet(Operation op)349     public int onGet(Operation op) {
350         notifyUpdateWakeLock();
351         sIsAborted = false;
352         HeaderSet request = null;
353         HeaderSet reply = new HeaderSet();
354         String type = "";
355         String name = "";
356         byte[] appParam = null;
357         AppParamValue appParamValue = new AppParamValue();
358         try {
359             request = op.getReceivedHeader();
360             type = (String)request.getHeader(HeaderSet.TYPE);
361             name = (String)request.getHeader(HeaderSet.NAME);
362             appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
363         } catch (IOException e) {
364             Log.e(TAG, "request headers error");
365             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
366         }
367 
368         /* TODO: block Get request if contacts are not completely loaded locally */
369 
370         if (V) logHeader(request);
371         if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name);
372 
373         if (type == null) {
374             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
375         }
376         // Accroding to specification,the name header could be omitted such as
377         // sony erriccsonHBH-DS980
378 
379         // For "x-bt/phonebook" and "x-bt/vcard-listing":
380         // if name == null, guess what carkit actually want from current path
381         // For "x-bt/vcard":
382         // We decide which kind of content client would like per current path
383 
384         boolean validName = true;
385         if (TextUtils.isEmpty(name)) {
386             validName = false;
387         }
388 
389         if (!validName || (validName && type.equals(TYPE_VCARD))) {
390             if (D) Log.d(TAG, "Guess what carkit actually want from current path (" +
391                     mCurrentPath + ")");
392 
393             if (mCurrentPath.equals(PB_PATH)) {
394                 appParamValue.needTag = ContentType.PHONEBOOK;
395             } else if (mCurrentPath.equals(ICH_PATH)) {
396                 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
397             } else if (mCurrentPath.equals(OCH_PATH)) {
398                 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
399             } else if (mCurrentPath.equals(MCH_PATH)) {
400                 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
401                 mNeedNewMissedCallsNum = true;
402             } else if (mCurrentPath.equals(CCH_PATH)) {
403                 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
404             } else if (mCurrentPath.equals(TELECOM_PATH)) {
405                 /* PBAP 1.1.1 change */
406                 if (!validName && type.equals(TYPE_LISTING)) {
407                     Log.e(TAG, "invalid vcard listing request in default folder");
408                     return ResponseCodes.OBEX_HTTP_NOT_FOUND;
409                 }
410             } else {
411                 Log.w(TAG, "mCurrentpath is not valid path!!!");
412                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
413             }
414             if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
415         } else {
416             // Not support SIM card currently
417             if (name.contains(SIM1.subSequence(0, SIM1.length()))) {
418                 Log.w(TAG, "Not support access SIM card info!");
419                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
420             }
421 
422             // we have weak name checking here to provide better
423             // compatibility with other devices,although unique name such as
424             // "pb.vcf" is required by SIG spec.
425             if (isNameMatchTarget(name, PB)) {
426                 appParamValue.needTag = ContentType.PHONEBOOK;
427                 if (D) Log.v(TAG, "download phonebook request");
428             } else if (isNameMatchTarget(name, ICH)) {
429                 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
430                 appParamValue.callHistoryVersionCounter =
431                         mVcardManager.getCallHistoryPrimaryFolderVersion(
432                                 ContentType.INCOMING_CALL_HISTORY);
433                 if (D) Log.v(TAG, "download incoming calls request");
434             } else if (isNameMatchTarget(name, OCH)) {
435                 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
436                 appParamValue.callHistoryVersionCounter =
437                         mVcardManager.getCallHistoryPrimaryFolderVersion(
438                                 ContentType.OUTGOING_CALL_HISTORY);
439                 if (D) Log.v(TAG, "download outgoing calls request");
440             } else if (isNameMatchTarget(name, MCH)) {
441                 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
442                 appParamValue.callHistoryVersionCounter =
443                         mVcardManager.getCallHistoryPrimaryFolderVersion(
444                                 ContentType.MISSED_CALL_HISTORY);
445                 mNeedNewMissedCallsNum = true;
446                 if (D) Log.v(TAG, "download missed calls request");
447             } else if (isNameMatchTarget(name, CCH)) {
448                 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
449                 appParamValue.callHistoryVersionCounter =
450                         mVcardManager.getCallHistoryPrimaryFolderVersion(
451                                 ContentType.COMBINED_CALL_HISTORY);
452                 if (D) Log.v(TAG, "download combined calls request");
453             } else {
454                 Log.w(TAG, "Input name doesn't contain valid info!!!");
455                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
456             }
457         }
458 
459         if ((appParam != null) && !parseApplicationParameter(appParam, appParamValue)) {
460             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
461         }
462 
463         // listing request
464         if (type.equals(TYPE_LISTING)) {
465             return pullVcardListing(appParam, appParamValue, reply, op, name);
466         }
467         // pull vcard entry request
468         else if (type.equals(TYPE_VCARD)) {
469             return pullVcardEntry(appParam, appParamValue, op, reply, name, mCurrentPath);
470         }
471         // down load phone book request
472         else if (type.equals(TYPE_PB)) {
473             return pullPhonebook(appParam, appParamValue, reply, op, name);
474         } else {
475             Log.w(TAG, "unknown type request!!!");
476             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
477         }
478     }
479 
isNameMatchTarget(String name, String target)480     private boolean isNameMatchTarget(String name, String target) {
481         String contentTypeName = name;
482         if (contentTypeName.endsWith(".vcf")) {
483             contentTypeName = contentTypeName
484                     .substring(0, contentTypeName.length() - ".vcf".length());
485         }
486         // There is a test case: Client will send a wrong name "/telecom/pbpb".
487         // So we must use the String between '/' and '/' as a indivisible part
488         // for comparing.
489         String[] nameList = contentTypeName.split("/");
490         for (String subName : nameList) {
491             if (subName.equals(target)) {
492                 return true;
493             }
494         }
495         return false;
496     }
497 
498     /** check whether path is legal */
isLegalPath(final String str)499     private final boolean isLegalPath(final String str) {
500         if (str.length() == 0) {
501             return true;
502         }
503         for (int i = 0; i < LEGAL_PATH.length; i++) {
504             if (str.equals(LEGAL_PATH[i])) {
505                 return true;
506             }
507         }
508         return false;
509     }
510 
511     private class AppParamValue {
512         public int maxListCount;
513 
514         public int listStartOffset;
515 
516         public String searchValue;
517 
518         // Indicate which vCard parameter the search operation shall be carried
519         // out on. Can be "Name | Number | Sound", default value is "Name".
520         public String searchAttr;
521 
522         // Indicate which sorting order shall be used for the
523         // <x-bt/vcard-listing> listing object.
524         // Can be "Alphabetical | Indexed | Phonetical", default value is
525         // "Indexed".
526         public String order;
527 
528         public int needTag;
529 
530         public boolean vcard21;
531 
532         public byte[] propertySelector;
533 
534         public byte[] supportedFeature;
535 
536         public boolean ignorefilter;
537 
538         public byte[] vCardSelector;
539 
540         public String vCardSelectorOperator;
541 
542         public byte[] callHistoryVersionCounter;
543 
AppParamValue()544         public AppParamValue() {
545             maxListCount = 0xFFFF;
546             listStartOffset = 0;
547             searchValue = "";
548             searchAttr = "";
549             order = "";
550             needTag = 0x00;
551             vcard21 = true;
552             //Filter is not set by default
553             ignorefilter = true;
554             vCardSelectorOperator = "0";
555             propertySelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
556             vCardSelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
557             supportedFeature = new byte[] {0x00, 0x00, 0x00, 0x00};
558         }
559 
dump()560         public void dump() {
561             Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
562                             + " searchValue=" + searchValue + " searchAttr=" + searchAttr
563                             + " needTag=" + needTag + " vcard21=" + vcard21 + " order=" + order
564                             + "vcardselector=" + vCardSelector + "vcardselop="
565                             + vCardSelectorOperator);
566         }
567     }
568 
569     /** To parse obex application parameter */
parseApplicationParameter(final byte[] appParam, AppParamValue appParamValue)570     private final boolean parseApplicationParameter(final byte[] appParam,
571             AppParamValue appParamValue) {
572         int i = 0;
573         boolean parseOk = true;
574         while ((i < appParam.length) && (parseOk == true)) {
575             switch (appParam[i]) {
576                 case ApplicationParameter.TRIPLET_TAGID.PROPERTY_SELECTOR_TAGID:
577                     i += 2; // length and tag field in triplet
578                     for (int index = 0;
579                             index < ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH;
580                             index++) {
581                         if (appParam[i+index] != 0){
582                             appParamValue.ignorefilter = false;
583                             appParamValue.propertySelector[index] = appParam[i + index];
584                         }
585                     }
586                     i += ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH;
587                     break;
588                 case ApplicationParameter.TRIPLET_TAGID.SUPPORTEDFEATURE_TAGID:
589                     i += 2; // length and tag field in triplet
590                     for (int index = 0;
591                             index < ApplicationParameter.TRIPLET_LENGTH.SUPPORTEDFEATURE_LENGTH;
592                             index++) {
593                         if (appParam[i + index] != 0) {
594                             appParamValue.supportedFeature[index] = appParam[i + index];
595                         }
596                     }
597 
598                     i += ApplicationParameter.TRIPLET_LENGTH.SUPPORTEDFEATURE_LENGTH;
599                     break;
600 
601                 case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID:
602                     i += 2; // length and tag field in triplet
603                     appParamValue.order = Byte.toString(appParam[i]);
604                     i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH;
605                     break;
606                 case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID:
607                     i += 1; // length field in triplet
608                     // length of search value is variable
609                     int length = appParam[i];
610                     if (length == 0) {
611                         parseOk = false;
612                         break;
613                     }
614                     if (appParam[i+length] == 0x0) {
615                         appParamValue.searchValue = new String(appParam, i + 1, length-1);
616                     } else {
617                         appParamValue.searchValue = new String(appParam, i + 1, length);
618                     }
619                     i += length;
620                     i += 1;
621                     break;
622                 case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
623                     i += 2;
624                     appParamValue.searchAttr = Byte.toString(appParam[i]);
625                     i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
626                     break;
627                 case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
628                     i += 2;
629                     if (appParam[i] == 0 && appParam[i + 1] == 0) {
630                         mNeedPhonebookSize = true;
631                     } else {
632                         int highValue = appParam[i] & 0xff;
633                         int lowValue = appParam[i + 1] & 0xff;
634                         appParamValue.maxListCount = highValue * 256 + lowValue;
635                     }
636                     i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
637                     break;
638                 case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
639                     i += 2;
640                     int highValue = appParam[i] & 0xff;
641                     int lowValue = appParam[i + 1] & 0xff;
642                     appParamValue.listStartOffset = highValue * 256 + lowValue;
643                     i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
644                     break;
645                 case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
646                     i += 2;// length field in triplet
647                     if (appParam[i] != 0) {
648                         appParamValue.vcard21 = false;
649                     }
650                     i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
651                     break;
652 
653                 case ApplicationParameter.TRIPLET_TAGID.VCARDSELECTOR_TAGID:
654                     i += 2;
655                     for (int index = 0;
656                             index < ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOR_LENGTH;
657                             index++) {
658                         if (appParam[i + index] != 0) {
659                             mVcardSelector = true;
660                             appParamValue.vCardSelector[index] = appParam[i + index];
661                         }
662                     }
663                     i += ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOR_LENGTH;
664                     break;
665                 case ApplicationParameter.TRIPLET_TAGID.VCARDSELECTOROPERATOR_TAGID:
666                     i += 2;
667                     appParamValue.vCardSelectorOperator = Byte.toString(appParam[i]);
668                     i += ApplicationParameter.TRIPLET_LENGTH.VCARDSELECTOROPERATOR_LENGTH;
669                     break;
670                 default:
671                     parseOk = false;
672                     Log.e(TAG, "Parse Application Parameter error");
673                     break;
674             }
675         }
676 
677         if (D) appParamValue.dump();
678 
679         return parseOk;
680     }
681 
682     /** Form and Send an XML format String to client for Phone book listing */
sendVcardListingXml( AppParamValue appParamValue, Operation op, int needSendBody, int size)683     private final int sendVcardListingXml(
684             AppParamValue appParamValue, Operation op, int needSendBody, int size) {
685         StringBuilder result = new StringBuilder();
686         int itemsFound = 0;
687         result.append("<?xml version=\"1.0\"?>");
688         result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
689         result.append("<vCard-listing version=\"1.0\">");
690 
691         // Phonebook listing request
692         if (appParamValue.needTag == ContentType.PHONEBOOK) {
693             String type = "";
694             if (appParamValue.searchAttr.equals("0")) {
695                 type = "name";
696             } else if (appParamValue.searchAttr.equals("1")) {
697                 type = "number";
698             }
699             if (type.length() > 0) {
700                 itemsFound = createList(appParamValue, needSendBody, size, result, type);
701             } else {
702                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
703             }
704         }
705         // Call history listing request
706         else {
707             ArrayList<String> nameList = mVcardManager.loadCallHistoryList(appParamValue.needTag);
708             int requestSize = nameList.size() >= appParamValue.maxListCount
709                     ? appParamValue.maxListCount
710                     : nameList.size();
711             int startPoint = appParamValue.listStartOffset;
712             int endPoint = startPoint + requestSize;
713             if (endPoint > nameList.size()) {
714                 endPoint = nameList.size();
715             }
716             if (D)
717                 Log.d(TAG, "call log list, size=" + requestSize + " offset="
718                                 + appParamValue.listStartOffset);
719 
720             for (int j = startPoint; j < endPoint; j++) {
721                 writeVCardEntry(j+1, nameList.get(j),result);
722             }
723         }
724         result.append("</vCard-listing>");
725 
726         if (D) Log.d(TAG, "itemsFound =" + itemsFound);
727 
728         return pushBytes(op, result.toString());
729     }
730 
createList(AppParamValue appParamValue, int needSendBody, int size, StringBuilder result, String type)731     private int createList(AppParamValue appParamValue, int needSendBody, int size,
732             StringBuilder result, String type) {
733         int itemsFound = 0;
734 
735         ArrayList<String> nameList = null;
736         if (mVcardSelector) {
737             nameList = mVcardManager.getSelectedPhonebookNameList(mOrderBy, appParamValue.vcard21,
738                     needSendBody, size, appParamValue.vCardSelector,
739                     appParamValue.vCardSelectorOperator);
740         } else {
741             nameList = mVcardManager.getPhonebookNameList(mOrderBy);
742         }
743 
744         final int requestSize = nameList.size() >= appParamValue.maxListCount
745                 ? appParamValue.maxListCount
746                 : nameList.size();
747         final int listSize = nameList.size();
748         String compareValue = "", currentValue;
749 
750         if (D)
751             Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset="
752                             + appParamValue.listStartOffset + " searchValue="
753                             + appParamValue.searchValue);
754 
755         if (type.equals("number")) {
756             // query the number, to get the names
757             ArrayList<String> names =
758                     mVcardManager.getContactNamesByNumber(appParamValue.searchValue);
759             for (int i = 0; i < names.size(); i++) {
760                 compareValue = names.get(i).trim();
761                 if (D) Log.d(TAG, "compareValue=" + compareValue);
762                 for (int pos = appParamValue.listStartOffset;
763                         pos < listSize && itemsFound < requestSize; pos++) {
764                     currentValue = nameList.get(pos);
765                     if (D) Log.d(TAG, "currentValue=" + currentValue);
766                     if (currentValue.equals(compareValue)) {
767                         itemsFound++;
768                         if (currentValue.contains(","))
769                            currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
770                         writeVCardEntry(pos, currentValue,result);
771                     }
772                 }
773                 if (itemsFound >= requestSize) {
774                     break;
775                 }
776             }
777         } else {
778             if (appParamValue.searchValue != null) {
779                 compareValue = appParamValue.searchValue.trim().toLowerCase();
780             }
781             for (int pos = appParamValue.listStartOffset;
782                     pos < listSize && itemsFound < requestSize; pos++) {
783                 currentValue = nameList.get(pos);
784                 if (currentValue.contains(","))
785                     currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
786 
787                 if (appParamValue.searchValue.isEmpty()
788                         || ((currentValue.toLowerCase()).startsWith(compareValue))) {
789                     itemsFound++;
790                     writeVCardEntry(pos, currentValue,result);
791                 }
792             }
793         }
794         return itemsFound;
795     }
796 
797     /**
798      * Function to send obex header back to client such as get phonebook size
799      * request
800      */
pushHeader(final Operation op, final HeaderSet reply)801     private final int pushHeader(final Operation op, final HeaderSet reply) {
802         OutputStream outputStream = null;
803 
804         if (D) Log.d(TAG, "Push Header");
805         if (D) Log.d(TAG, reply.toString());
806 
807         int pushResult = ResponseCodes.OBEX_HTTP_OK;
808         try {
809             op.sendHeaders(reply);
810             outputStream = op.openOutputStream();
811             outputStream.flush();
812         } catch (IOException e) {
813             Log.e(TAG, e.toString());
814             pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
815         } finally {
816             if (!closeStream(outputStream, op)) {
817                 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
818             }
819         }
820         return pushResult;
821     }
822 
823     /** Function to send vcard data to client */
pushBytes(Operation op, final String vcardString)824     private final int pushBytes(Operation op, final String vcardString) {
825         if (vcardString == null) {
826             Log.w(TAG, "vcardString is null!");
827             return ResponseCodes.OBEX_HTTP_OK;
828         }
829 
830         OutputStream outputStream = null;
831         int pushResult = ResponseCodes.OBEX_HTTP_OK;
832         try {
833             outputStream = op.openOutputStream();
834             outputStream.write(vcardString.getBytes());
835             if (V) Log.v(TAG, "Send Data complete!");
836         } catch (IOException e) {
837             Log.e(TAG, "open/write outputstrem failed" + e.toString());
838             pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
839         }
840 
841         if (!closeStream(outputStream, op)) {
842             pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
843         }
844 
845         return pushResult;
846     }
847 
handleAppParaForResponse( AppParamValue appParamValue, int size, HeaderSet reply, Operation op, String name)848     private final int handleAppParaForResponse(
849             AppParamValue appParamValue, int size, HeaderSet reply, Operation op, String name) {
850         byte[] misnum = new byte[1];
851         ApplicationParameter ap = new ApplicationParameter();
852         boolean needSendCallHistoryVersionCounters = false;
853         if (isNameMatchTarget(name, MCH) || isNameMatchTarget(name, ICH)
854                 || isNameMatchTarget(name, OCH) || isNameMatchTarget(name, CCH))
855             needSendCallHistoryVersionCounters =
856                     checkPbapFeatureSupport(folderVersionCounterbitMask);
857         boolean needSendPhonebookVersionCounters = false;
858         if (isNameMatchTarget(name, PB))
859             needSendPhonebookVersionCounters = checkPbapFeatureSupport(folderVersionCounterbitMask);
860 
861         // In such case, PCE only want the number of index.
862         // So response not contain any Body header.
863         if (mNeedPhonebookSize) {
864             if (D) Log.d(TAG, "Need Phonebook size in response header.");
865             mNeedPhonebookSize = false;
866 
867             byte[] pbsize = new byte[2];
868 
869             pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
870             pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
871             ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
872                     ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
873 
874             if (mNeedNewMissedCallsNum) {
875                 mNeedNewMissedCallsNum = false;
876                 int nmnum = 0;
877                 ContentResolver contentResolver;
878                 contentResolver = mContext.getContentResolver();
879 
880                 Cursor c = contentResolver.query(
881                     Calls.CONTENT_URI,
882                     null,
883                     Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1",
884                     null,
885                     Calls.DEFAULT_SORT_ORDER);
886 
887                 if (c != null) {
888                   nmnum = c.getCount();
889                   c.close();
890                 }
891 
892                 nmnum = nmnum > 0 ? nmnum : 0;
893                 misnum[0] = (byte)nmnum;
894                 if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
895             }
896 
897             if (checkPbapFeatureSupport(databaseIdentifierBitMask)) {
898                 setDbCounters(ap);
899             }
900             if (needSendPhonebookVersionCounters) {
901                 setFolderVersionCounters(ap);
902             }
903             if (needSendCallHistoryVersionCounters) {
904                 setCallversionCounters(ap, appParamValue);
905             }
906             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
907 
908             if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
909 
910             return pushHeader(op, reply);
911         }
912 
913         // Only apply to "mch" download/listing.
914         // NewMissedCalls is used only in the response, together with Body
915         // header.
916         if (mNeedNewMissedCallsNum) {
917             if (D) Log.d(TAG, "Need new missed call num in response header.");
918             mNeedNewMissedCallsNum = false;
919             int nmnum = 0;
920             ContentResolver contentResolver;
921             contentResolver = mContext.getContentResolver();
922 
923             Cursor c = contentResolver.query(
924                 Calls.CONTENT_URI,
925                 null,
926                 Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1",
927                 null,
928                 Calls.DEFAULT_SORT_ORDER);
929 
930             if (c != null) {
931               nmnum = c.getCount();
932               c.close();
933             }
934 
935             nmnum = nmnum > 0 ? nmnum : 0;
936             misnum[0] = (byte)nmnum;
937             if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
938 
939             ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
940                     ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
941             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
942             if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
943                         + nmnum);
944 
945             // Only Specifies the headers, not write for now, will write to PCE
946             // together with Body
947             try {
948                 op.sendHeaders(reply);
949             } catch (IOException e) {
950                 Log.e(TAG, e.toString());
951                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
952             }
953         }
954 
955         if (checkPbapFeatureSupport(databaseIdentifierBitMask)) {
956             setDbCounters(ap);
957             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
958             try {
959                 op.sendHeaders(reply);
960             } catch (IOException e) {
961                 Log.e(TAG, e.toString());
962                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
963             }
964         }
965 
966         if (needSendPhonebookVersionCounters) {
967             setFolderVersionCounters(ap);
968             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
969             try {
970                 op.sendHeaders(reply);
971             } catch (IOException e) {
972                 Log.e(TAG, e.toString());
973                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
974             }
975         }
976 
977         if (needSendCallHistoryVersionCounters) {
978             setCallversionCounters(ap, appParamValue);
979             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
980             try {
981                 op.sendHeaders(reply);
982             } catch (IOException e) {
983                 Log.e(TAG, e.toString());
984                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
985             }
986         }
987 
988         return NEED_SEND_BODY;
989     }
990 
pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, String name)991     private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
992             HeaderSet reply, Operation op, String name) {
993         String searchAttr = appParamValue.searchAttr.trim();
994 
995         if (searchAttr == null || searchAttr.length() == 0) {
996             // If searchAttr is not set by PCE, set default value per spec.
997             appParamValue.searchAttr = "0";
998             if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
999         } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
1000             Log.w(TAG, "search attr not supported");
1001             if (searchAttr.equals("2")) {
1002                 // search by sound is not supported currently
1003                 Log.w(TAG, "do not support search by sound");
1004                 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1005             }
1006             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1007         } else {
1008             Log.i(TAG, "searchAttr is valid: " + searchAttr);
1009         }
1010 
1011         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1012         int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1013         if (needSendBody != NEED_SEND_BODY) {
1014             op.noBodyHeader();
1015             return needSendBody;
1016         }
1017 
1018         if (size == 0) {
1019             if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1020             return ResponseCodes.OBEX_HTTP_OK;
1021         }
1022 
1023         String orderPara = appParamValue.order.trim();
1024         if (TextUtils.isEmpty(orderPara)) {
1025             // If order parameter is not set by PCE, set default value per spec.
1026             orderPara = "0";
1027             if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
1028                        "Assume order by 'Indexed' by default");
1029         } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
1030             if (D) Log.d(TAG, "Order parameter is not supported: " + appParamValue.order);
1031             if (orderPara.equals("2")) {
1032                 // Order by sound is not supported currently
1033                 Log.w(TAG, "Do not support order by sound");
1034                 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1035             }
1036             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1037         } else {
1038             Log.i(TAG, "Order parameter is valid: " + orderPara);
1039         }
1040 
1041         if (orderPara.equals("0")) {
1042             mOrderBy = ORDER_BY_INDEXED;
1043         } else if (orderPara.equals("1")) {
1044             mOrderBy = ORDER_BY_ALPHABETICAL;
1045         }
1046 
1047         return sendVcardListingXml(appParamValue, op, needSendBody, size);
1048     }
1049 
pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op, HeaderSet reply, final String name, final String current_path)1050     private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op,
1051             HeaderSet reply, final String name, final String current_path) {
1052         if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
1053             if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
1054             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1055         }
1056         String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
1057         int intIndex = 0;
1058         if (strIndex.trim().length() != 0) {
1059             try {
1060                 intIndex = Integer.parseInt(strIndex);
1061             } catch (NumberFormatException e) {
1062                 Log.e(TAG, "catch number format exception " + e.toString());
1063                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1064             }
1065         }
1066 
1067         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1068         int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1069         if (size == 0) {
1070             if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1071             return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1072         }
1073 
1074         boolean vcard21 = appParamValue.vcard21;
1075         if (appParamValue.needTag == 0) {
1076             Log.w(TAG, "wrong path!");
1077             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1078         } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
1079             if (intIndex < 0 || intIndex >= size) {
1080                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1081                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1082             } else if (intIndex == 0) {
1083                 // For PB_PATH, 0.vcf is the phone number of this phone.
1084                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null);
1085                 return pushBytes(op, ownerVcard);
1086             } else {
1087                 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
1088                         mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector);
1089             }
1090         } else {
1091             if (intIndex <= 0 || intIndex > size) {
1092                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1093                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1094             }
1095             // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
1096             // begin from 1.vcf
1097             if (intIndex >= 1) {
1098                 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1099                         intIndex, intIndex, vcard21, needSendBody, size, appParamValue.ignorefilter,
1100                         appParamValue.propertySelector, appParamValue.vCardSelector,
1101                         appParamValue.vCardSelectorOperator, mVcardSelector);
1102             }
1103         }
1104         return ResponseCodes.OBEX_HTTP_OK;
1105     }
1106 
pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, final String name)1107     private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
1108             Operation op, final String name) {
1109         // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
1110         if (name != null) {
1111             int dotIndex = name.indexOf(".");
1112             String vcf = "vcf";
1113             if (dotIndex >= 0 && dotIndex <= name.length()) {
1114                 if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
1115                     Log.w(TAG, "name is not .vcf");
1116                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1117                 }
1118             }
1119         } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
1120 
1121         int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
1122         int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name);
1123         if (needSendBody != NEED_SEND_BODY) {
1124             op.noBodyHeader();
1125             return needSendBody;
1126         }
1127 
1128         if (pbSize == 0) {
1129             if (D) Log.d(TAG, "PhonebookSize is 0, return.");
1130             return ResponseCodes.OBEX_HTTP_OK;
1131         }
1132 
1133         int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
1134                 : pbSize;
1135         int startPoint = appParamValue.listStartOffset;
1136         if (startPoint < 0 || startPoint >= pbSize) {
1137             Log.w(TAG, "listStartOffset is not correct! " + startPoint);
1138             return ResponseCodes.OBEX_HTTP_OK;
1139         }
1140 
1141         // Limit the number of call log to CALLLOG_NUM_LIMIT
1142         if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1143             if (requestSize > CALLLOG_NUM_LIMIT) {
1144                requestSize = CALLLOG_NUM_LIMIT;
1145             }
1146         }
1147 
1148         int endPoint = startPoint + requestSize - 1;
1149         if (endPoint > pbSize - 1) {
1150             endPoint = pbSize - 1;
1151         }
1152         if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
1153                 startPoint + " endPoint=" + endPoint);
1154 
1155         boolean vcard21 = appParamValue.vcard21;
1156         if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1157             if (startPoint == 0) {
1158                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null);
1159                 if (endPoint == 0) {
1160                     return pushBytes(op, ownerVcard);
1161                 } else {
1162                     return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
1163                             ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter,
1164                             appParamValue.propertySelector, appParamValue.vCardSelector,
1165                             appParamValue.vCardSelectorOperator, mVcardSelector);
1166                 }
1167             } else {
1168                 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
1169                         vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter,
1170                         appParamValue.propertySelector, appParamValue.vCardSelector,
1171                         appParamValue.vCardSelectorOperator, mVcardSelector);
1172             }
1173         } else {
1174             return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1175                     startPoint + 1, endPoint + 1, vcard21, needSendBody, pbSize,
1176                     appParamValue.ignorefilter, appParamValue.propertySelector,
1177                     appParamValue.vCardSelector, appParamValue.vCardSelectorOperator,
1178                     mVcardSelector);
1179         }
1180     }
1181 
closeStream(final OutputStream out, final Operation op)1182     public static boolean closeStream(final OutputStream out, final Operation op) {
1183         boolean returnvalue = true;
1184         try {
1185             if (out != null) {
1186                 out.close();
1187             }
1188         } catch (IOException e) {
1189             Log.e(TAG, "outputStream close failed" + e.toString());
1190             returnvalue = false;
1191         }
1192         try {
1193             if (op != null) {
1194                 op.close();
1195             }
1196         } catch (IOException e) {
1197             Log.e(TAG, "operation close failed" + e.toString());
1198             returnvalue = false;
1199         }
1200         return returnvalue;
1201     }
1202 
1203     // Reserved for future use. In case PSE challenge PCE and PCE input wrong
1204     // session key.
onAuthenticationFailure(final byte[] userName)1205     public final void onAuthenticationFailure(final byte[] userName) {
1206     }
1207 
createSelectionPara(final int type)1208     public static final String createSelectionPara(final int type) {
1209         String selection = null;
1210         switch (type) {
1211             case ContentType.INCOMING_CALL_HISTORY:
1212                 selection = "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR "
1213                         + Calls.TYPE + "=" + CallLog.Calls.REJECTED_TYPE + ")";
1214                 break;
1215             case ContentType.OUTGOING_CALL_HISTORY:
1216                 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
1217                 break;
1218             case ContentType.MISSED_CALL_HISTORY:
1219                 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
1220                 break;
1221             default:
1222                 break;
1223         }
1224         if (V) Log.v(TAG, "Call log selection: " + selection);
1225         return selection;
1226     }
1227 
1228     /**
1229      * XML encode special characters in the name field
1230      */
xmlEncode(String name, StringBuilder result)1231     private void xmlEncode(String name, StringBuilder result) {
1232         if (name == null) {
1233             return;
1234         }
1235 
1236         final StringCharacterIterator iterator = new StringCharacterIterator(name);
1237         char character =  iterator.current();
1238         while (character != CharacterIterator.DONE ){
1239             if (character == '<') {
1240                 result.append("&lt;");
1241             }
1242             else if (character == '>') {
1243                 result.append("&gt;");
1244             }
1245             else if (character == '\"') {
1246                 result.append("&quot;");
1247             }
1248             else if (character == '\'') {
1249                 result.append("&#039;");
1250             }
1251             else if (character == '&') {
1252                 result.append("&amp;");
1253             }
1254             else {
1255                 // The char is not a special one, add it to the result as is
1256                 result.append(character);
1257             }
1258             character = iterator.next();
1259         }
1260     }
1261 
writeVCardEntry(int vcfIndex, String name, StringBuilder result)1262     private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
1263         result.append("<card handle=\"");
1264         result.append(vcfIndex);
1265         result.append(".vcf\" name=\"");
1266         xmlEncode(name, result);
1267         result.append("\"/>");
1268     }
1269 
notifyUpdateWakeLock()1270     private void notifyUpdateWakeLock() {
1271         Message msg = Message.obtain(mCallback);
1272         msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK;
1273         msg.sendToTarget();
1274     }
1275 
logHeader(HeaderSet hs)1276     public static final void logHeader(HeaderSet hs) {
1277         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1278         try {
1279 
1280             Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1281             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1282             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1283             Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1284             Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1285             Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1286             Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1287             Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1288             Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1289             Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1290             Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1291             Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1292         } catch (IOException e) {
1293             Log.e(TAG, "dump HeaderSet error " + e);
1294         }
1295     }
1296 
setDbCounters(ApplicationParameter ap)1297     private void setDbCounters(ApplicationParameter ap) {
1298         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.DATABASEIDENTIFIER_TAGID,
1299                 ApplicationParameter.TRIPLET_LENGTH.DATABASEIDENTIFIER_LENGTH,
1300                 getDatabaseIdentifier());
1301     }
1302 
setFolderVersionCounters(ApplicationParameter ap)1303     private void setFolderVersionCounters(ApplicationParameter ap) {
1304         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1305                 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1306                 getPBPrimaryFolderVersion());
1307         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1308                 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1309                 getPBSecondaryFolderVersion());
1310     }
1311 
setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue)1312     private void setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue) {
1313         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1314                 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1315                 appParamValue.callHistoryVersionCounter);
1316 
1317         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1318                 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1319                 appParamValue.callHistoryVersionCounter);
1320     }
1321 
getDatabaseIdentifier()1322     private byte[] getDatabaseIdentifier() {
1323         mDatabaseIdentifierHigh = 0;
1324         mDatabaseIdentifierLow = mService.getDbIdentifier();
1325         if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
1326                 && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
1327             ByteBuffer ret = ByteBuffer.allocate(16);
1328             ret.putLong(mDatabaseIdentifierHigh);
1329             ret.putLong(mDatabaseIdentifierLow);
1330             return ret.array();
1331         } else {
1332             return null;
1333         }
1334     }
1335 
getPBPrimaryFolderVersion()1336     private byte[] getPBPrimaryFolderVersion() {
1337         long primaryVcMsb = 0;
1338         ByteBuffer pvc = ByteBuffer.allocate(16);
1339         pvc.putLong(primaryVcMsb);
1340 
1341         Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.primaryVersionCounter);
1342         pvc.putLong(BluetoothPbapUtils.primaryVersionCounter);
1343         return pvc.array();
1344     }
1345 
getPBSecondaryFolderVersion()1346     private byte[] getPBSecondaryFolderVersion() {
1347         long secondaryVcMsb = 0;
1348         ByteBuffer svc = ByteBuffer.allocate(16);
1349         svc.putLong(secondaryVcMsb);
1350 
1351         Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.secondaryVersionCounter);
1352         svc.putLong(BluetoothPbapUtils.secondaryVersionCounter);
1353         return svc.array();
1354     }
1355 
checkPbapFeatureSupport(long featureBitMask)1356     private boolean checkPbapFeatureSupport(long featureBitMask) {
1357         Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask);
1358         return ((ByteBuffer.wrap(connAppParamValue.supportedFeature).getInt() & featureBitMask)
1359                 != 0);
1360     }
1361 }
1362