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