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                 ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
990                         ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
991                 if (D) {
992                     Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
993                             + nmnum);
994                 }
995             }
996 
997             if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
998                 setDbCounters(ap);
999             }
1000             if (needSendPhonebookVersionCounters) {
1001                 setFolderVersionCounters(ap);
1002             }
1003             if (needSendCallHistoryVersionCounters) {
1004                 setCallversionCounters(ap, appParamValue);
1005             }
1006             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1007 
1008             if (D) {
1009                 Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
1010             }
1011 
1012             return pushHeader(op, reply);
1013         }
1014 
1015         // Only apply to "mch" download/listing.
1016         // NewMissedCalls is used only in the response, together with Body
1017         // header.
1018         if (mNeedNewMissedCallsNum) {
1019             if (D) {
1020                 Log.d(TAG, "Need new missed call num in response header.");
1021             }
1022             mNeedNewMissedCallsNum = false;
1023             int nmnum = 0;
1024             ContentResolver contentResolver;
1025             contentResolver = mContext.getContentResolver();
1026 
1027             Cursor c = contentResolver.query(Calls.CONTENT_URI, null,
1028                     Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND "
1029                             + android.provider.CallLog.Calls.NEW + " = 1", null,
1030                     Calls.DEFAULT_SORT_ORDER);
1031 
1032             if (c != null) {
1033                 nmnum = c.getCount();
1034                 c.close();
1035             }
1036 
1037             nmnum = nmnum > 0 ? nmnum : 0;
1038             misnum[0] = (byte) nmnum;
1039             if (D) {
1040                 Log.d(TAG,
1041                         "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
1042             }
1043 
1044             ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
1045                     ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
1046             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1047             if (D) {
1048                 Log.d(TAG,
1049                         "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
1050             }
1051 
1052             // Only Specifies the headers, not write for now, will write to PCE
1053             // together with Body
1054             try {
1055                 op.sendHeaders(reply);
1056             } catch (IOException e) {
1057                 Log.e(TAG, e.toString());
1058                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
1059             }
1060         }
1061 
1062         if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
1063             setDbCounters(ap);
1064             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1065             try {
1066                 op.sendHeaders(reply);
1067             } catch (IOException e) {
1068                 Log.e(TAG, e.toString());
1069                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
1070             }
1071         }
1072 
1073         if (needSendPhonebookVersionCounters) {
1074             setFolderVersionCounters(ap);
1075             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1076             try {
1077                 op.sendHeaders(reply);
1078             } catch (IOException e) {
1079                 Log.e(TAG, e.toString());
1080                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
1081             }
1082         }
1083 
1084         if (needSendCallHistoryVersionCounters) {
1085             setCallversionCounters(ap, appParamValue);
1086             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1087             try {
1088                 op.sendHeaders(reply);
1089             } catch (IOException e) {
1090                 Log.e(TAG, e.toString());
1091                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
1092             }
1093         }
1094 
1095         return NEED_SEND_BODY;
1096     }
1097 
pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, String name)1098     private int pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
1099             Operation op, String name) {
1100         String searchAttr = appParamValue.searchAttr.trim();
1101 
1102         if (searchAttr == null || searchAttr.length() == 0) {
1103             // If searchAttr is not set by PCE, set default value per spec.
1104             appParamValue.searchAttr = "0";
1105             if (D) {
1106                 Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
1107             }
1108         } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
1109             Log.w(TAG, "search attr not supported");
1110             if (searchAttr.equals("2")) {
1111                 // search by sound is not supported currently
1112                 Log.w(TAG, "do not support search by sound");
1113                 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1114             }
1115             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1116         } else {
1117             Log.i(TAG, "searchAttr is valid: " + searchAttr);
1118         }
1119 
1120         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1121         int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1122         if (needSendBody != NEED_SEND_BODY) {
1123             op.noBodyHeader();
1124             return needSendBody;
1125         }
1126 
1127         if (size == 0) {
1128             if (D) {
1129                 Log.d(TAG, "PhonebookSize is 0, return.");
1130             }
1131             return ResponseCodes.OBEX_HTTP_OK;
1132         }
1133 
1134         String orderPara = appParamValue.order.trim();
1135         if (TextUtils.isEmpty(orderPara)) {
1136             // If order parameter is not set by PCE, set default value per spec.
1137             orderPara = "0";
1138             if (D) {
1139                 Log.d(TAG, "Order parameter is not set by PCE. "
1140                         + "Assume order by 'Indexed' by default");
1141             }
1142         } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
1143             if (D) {
1144                 Log.d(TAG, "Order parameter is not supported: " + appParamValue.order);
1145             }
1146             if (orderPara.equals("2")) {
1147                 // Order by sound is not supported currently
1148                 Log.w(TAG, "Do not support order by sound");
1149                 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1150             }
1151             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1152         } else {
1153             Log.i(TAG, "Order parameter is valid: " + orderPara);
1154         }
1155 
1156         if (orderPara.equals("0")) {
1157             mOrderBy = ORDER_BY_INDEXED;
1158         } else if (orderPara.equals("1")) {
1159             mOrderBy = ORDER_BY_ALPHABETICAL;
1160         }
1161 
1162         return sendVcardListingXml(appParamValue, op, needSendBody, size);
1163     }
1164 
pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op, HeaderSet reply, final String name, final String currentPath)1165     private int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op,
1166             HeaderSet reply, final String name, final String currentPath) {
1167         if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
1168             if (D) {
1169                 Log.d(TAG, "Name is Null, or the length of name < 5 !");
1170             }
1171             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1172         }
1173         String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
1174         int intIndex = 0;
1175         if (strIndex.trim().length() != 0) {
1176             try {
1177                 intIndex = Integer.parseInt(strIndex);
1178             } catch (NumberFormatException e) {
1179                 Log.e(TAG, "catch number format exception " + e.toString());
1180                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1181             }
1182         }
1183 
1184         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1185         int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1186         if (size == 0) {
1187             if (D) {
1188                 Log.d(TAG, "PhonebookSize is 0, return.");
1189             }
1190             return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1191         }
1192 
1193         boolean vcard21 = appParamValue.vcard21;
1194         if (appParamValue.needTag == 0) {
1195             Log.w(TAG, "wrong path!");
1196             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1197         } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
1198             if (intIndex < 0 || intIndex >= size) {
1199                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1200                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1201             } else if (intIndex == 0) {
1202                 // For PB_PATH, 0.vcf is the phone number of this phone.
1203                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,
1204                         appParamValue.ignorefilter ? null : appParamValue.propertySelector);
1205                 return pushBytes(op, ownerVcard);
1206             } else {
1207                 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
1208                         mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector);
1209             }
1210         } else {
1211             if (intIndex <= 0 || intIndex > size) {
1212                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1213                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1214             }
1215             // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
1216             // begin from 1.vcf
1217             if (intIndex >= 1) {
1218                 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1219                         intIndex, intIndex, vcard21, needSendBody, size, appParamValue.ignorefilter,
1220                         appParamValue.propertySelector, appParamValue.vCardSelector,
1221                         appParamValue.vCardSelectorOperator, mVcardSelector);
1222             }
1223         }
1224         return ResponseCodes.OBEX_HTTP_OK;
1225     }
1226 
pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, final String name)1227     private int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
1228             Operation op, final String name) {
1229         // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
1230         if (name != null) {
1231             int dotIndex = name.indexOf(".");
1232             String vcf = "vcf";
1233             if (dotIndex >= 0 && dotIndex <= name.length()) {
1234                 if (!name.regionMatches(dotIndex + 1, vcf, 0, vcf.length())) {
1235                     Log.w(TAG, "name is not .vcf");
1236                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1237                 }
1238             }
1239         } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
1240 
1241         int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
1242         int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name);
1243         if (needSendBody != NEED_SEND_BODY) {
1244             op.noBodyHeader();
1245             return needSendBody;
1246         }
1247 
1248         if (pbSize == 0) {
1249             if (D) {
1250                 Log.d(TAG, "PhonebookSize is 0, return.");
1251             }
1252             return ResponseCodes.OBEX_HTTP_OK;
1253         }
1254 
1255         int requestSize =
1256                 pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
1257         int startPoint = appParamValue.listStartOffset;
1258         if (startPoint < 0 || startPoint >= pbSize) {
1259             Log.w(TAG, "listStartOffset is not correct! " + startPoint);
1260             return ResponseCodes.OBEX_HTTP_OK;
1261         }
1262 
1263         // Limit the number of call log to CALLLOG_NUM_LIMIT
1264         if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1265             if (requestSize > CALLLOG_NUM_LIMIT) {
1266                 requestSize = CALLLOG_NUM_LIMIT;
1267             }
1268         }
1269 
1270         int endPoint = startPoint + requestSize - 1;
1271         if (endPoint > pbSize - 1) {
1272             endPoint = pbSize - 1;
1273         }
1274         if (D) {
1275             Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + startPoint
1276                     + " endPoint=" + endPoint);
1277         }
1278 
1279         boolean vcard21 = appParamValue.vcard21;
1280         if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1281             if (startPoint == 0) {
1282                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,
1283                         appParamValue.ignorefilter ? null : appParamValue.propertySelector);
1284                 if (endPoint == 0) {
1285                     return pushBytes(op, ownerVcard);
1286                 } else {
1287                     return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
1288                             ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter,
1289                             appParamValue.propertySelector, appParamValue.vCardSelector,
1290                             appParamValue.vCardSelectorOperator, mVcardSelector);
1291                 }
1292             } else {
1293                 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
1294                         vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter,
1295                         appParamValue.propertySelector, appParamValue.vCardSelector,
1296                         appParamValue.vCardSelectorOperator, mVcardSelector);
1297             }
1298         } else {
1299             return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1300                     startPoint, endPoint, vcard21, needSendBody, pbSize,
1301                     appParamValue.ignorefilter, appParamValue.propertySelector,
1302                     appParamValue.vCardSelector, appParamValue.vCardSelectorOperator,
1303                     mVcardSelector);
1304         }
1305     }
1306 
closeStream(final OutputStream out, final Operation op)1307     public static boolean closeStream(final OutputStream out, final Operation op) {
1308         boolean returnvalue = true;
1309         try {
1310             if (out != null) {
1311                 out.close();
1312             }
1313         } catch (IOException e) {
1314             Log.e(TAG, "outputStream close failed" + e.toString());
1315             returnvalue = false;
1316         }
1317         try {
1318             if (op != null) {
1319                 op.close();
1320             }
1321         } catch (IOException e) {
1322             Log.e(TAG, "operation close failed" + e.toString());
1323             returnvalue = false;
1324         }
1325         return returnvalue;
1326     }
1327 
1328     // Reserved for future use. In case PSE challenge PCE and PCE input wrong
1329     // session key.
1330     @Override
onAuthenticationFailure(final byte[] userName)1331     public final void onAuthenticationFailure(final byte[] userName) {
1332     }
1333 
createSelectionPara(final int type)1334     public static final String createSelectionPara(final int type) {
1335         String selection = null;
1336         switch (type) {
1337             case ContentType.INCOMING_CALL_HISTORY:
1338                 selection =
1339                         "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR " + Calls.TYPE
1340                                 + "=" + CallLog.Calls.REJECTED_TYPE + ")";
1341                 break;
1342             case ContentType.OUTGOING_CALL_HISTORY:
1343                 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
1344                 break;
1345             case ContentType.MISSED_CALL_HISTORY:
1346                 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
1347                 break;
1348             default:
1349                 break;
1350         }
1351         if (V) {
1352             Log.v(TAG, "Call log selection: " + selection);
1353         }
1354         return selection;
1355     }
1356 
1357     /**
1358      * XML encode special characters in the name field
1359      */
xmlEncode(String name, StringBuilder result)1360     private void xmlEncode(String name, StringBuilder result) {
1361         if (name == null) {
1362             return;
1363         }
1364 
1365         final StringCharacterIterator iterator = new StringCharacterIterator(name);
1366         char character = iterator.current();
1367         while (character != CharacterIterator.DONE) {
1368             if (character == '<') {
1369                 result.append("&lt;");
1370             } else if (character == '>') {
1371                 result.append("&gt;");
1372             } else if (character == '\"') {
1373                 result.append("&quot;");
1374             } else if (character == '\'') {
1375                 result.append("&#039;");
1376             } else if (character == '&') {
1377                 result.append("&amp;");
1378             } else {
1379                 // The char is not a special one, add it to the result as is
1380                 result.append(character);
1381             }
1382             character = iterator.next();
1383         }
1384     }
1385 
writeVCardEntry(int vcfIndex, String name, StringBuilder result)1386     private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
1387         result.append("<card handle=\"");
1388         result.append(vcfIndex);
1389         result.append(".vcf\" name=\"");
1390         xmlEncode(name, result);
1391         result.append("\"/>");
1392     }
1393 
notifyUpdateWakeLock()1394     private void notifyUpdateWakeLock() {
1395         Message msg = Message.obtain(mCallback);
1396         msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK;
1397         msg.sendToTarget();
1398     }
1399 
logHeader(HeaderSet hs)1400     public static final void logHeader(HeaderSet hs) {
1401         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1402         try {
1403 
1404             Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1405             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1406             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1407             Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1408             Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1409             Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1410             Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1411             Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1412             Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1413             Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1414             Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1415             Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1416         } catch (IOException e) {
1417             Log.e(TAG, "dump HeaderSet error " + e);
1418         }
1419     }
1420 
setDbCounters(ApplicationParameter ap)1421     private void setDbCounters(ApplicationParameter ap) {
1422         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.DATABASEIDENTIFIER_TAGID,
1423                 ApplicationParameter.TRIPLET_LENGTH.DATABASEIDENTIFIER_LENGTH,
1424                 getDatabaseIdentifier());
1425     }
1426 
setFolderVersionCounters(ApplicationParameter ap)1427     private void setFolderVersionCounters(ApplicationParameter ap) {
1428         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1429                 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1430                 getPBPrimaryFolderVersion());
1431         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1432                 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1433                 getPBSecondaryFolderVersion());
1434     }
1435 
setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue)1436     private void setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue) {
1437         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1438                 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1439                 appParamValue.callHistoryVersionCounter);
1440 
1441         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1442                 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1443                 appParamValue.callHistoryVersionCounter);
1444     }
1445 
getDatabaseIdentifier()1446     private byte[] getDatabaseIdentifier() {
1447         mDatabaseIdentifierHigh = 0;
1448         mDatabaseIdentifierLow = BluetoothPbapUtils.sDbIdentifier.get();
1449         if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
1450                 && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
1451             ByteBuffer ret = ByteBuffer.allocate(16);
1452             ret.putLong(mDatabaseIdentifierHigh);
1453             ret.putLong(mDatabaseIdentifierLow);
1454             return ret.array();
1455         } else {
1456             return null;
1457         }
1458     }
1459 
getPBPrimaryFolderVersion()1460     private byte[] getPBPrimaryFolderVersion() {
1461         long primaryVcMsb = 0;
1462         ByteBuffer pvc = ByteBuffer.allocate(16);
1463         pvc.putLong(primaryVcMsb);
1464 
1465         Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter);
1466         pvc.putLong(BluetoothPbapUtils.sPrimaryVersionCounter);
1467         return pvc.array();
1468     }
1469 
getPBSecondaryFolderVersion()1470     private byte[] getPBSecondaryFolderVersion() {
1471         long secondaryVcMsb = 0;
1472         ByteBuffer svc = ByteBuffer.allocate(16);
1473         svc.putLong(secondaryVcMsb);
1474 
1475         Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.sSecondaryVersionCounter);
1476         svc.putLong(BluetoothPbapUtils.sSecondaryVersionCounter);
1477         return svc.array();
1478     }
1479 
checkPbapFeatureSupport(long featureBitMask)1480     private boolean checkPbapFeatureSupport(long featureBitMask) {
1481         Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask);
1482         return ((ByteBuffer.wrap(mConnAppParamValue.supportedFeature).getInt() & featureBitMask)
1483                 != 0);
1484     }
1485 }
1486