• 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             // query the number, to get the names
821             ArrayList<String> names =
822                     mVcardManager.getContactNamesByNumber(appParamValue.searchValue);
823             if (mOrderBy == ORDER_BY_ALPHABETICAL) Collections.sort(names);
824             for (int i = 0; i < names.size(); i++) {
825                 compareValue = names.get(i).trim();
826                 if (D) {
827                     Log.d(TAG, "compareValue=" + compareValue);
828                 }
829                 for (int pos = appParamValue.listStartOffset;
830                         pos < listSize && itemsFound < requestSize; pos++) {
831                     currentValue = nameList.get(pos);
832                     if (V) {
833                         Log.d(TAG, "currentValue=" + currentValue);
834                     }
835                     if (currentValue.equals(compareValue)) {
836                         itemsFound++;
837                         if (currentValue.contains(",")) {
838                             currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
839                         }
840                         writeVCardEntry(pos, currentValue, result);
841                     }
842                 }
843                 if (itemsFound >= requestSize) {
844                     break;
845                 }
846             }
847         } else {
848             if (appParamValue.searchValue != null) {
849                 compareValue = appParamValue.searchValue.trim().toLowerCase();
850             }
851             for (int pos = appParamValue.listStartOffset;
852                     pos < listSize && itemsFound < requestSize; pos++) {
853                 currentValue = nameList.get(pos);
854                 if (currentValue.contains(",")) {
855                     currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
856                 }
857 
858                 if (appParamValue.searchValue.isEmpty() || ((currentValue.toLowerCase()).startsWith(
859                         compareValue))) {
860                     itemsFound++;
861                     writeVCardEntry(pos, currentValue, result);
862                 }
863             }
864         }
865         return itemsFound;
866     }
867 
868     /**
869      * Function to send obex header back to client such as get phonebook size
870      * request
871      */
pushHeader(final Operation op, final HeaderSet reply)872     private int pushHeader(final Operation op, final HeaderSet reply) {
873         OutputStream outputStream = null;
874 
875         if (D) {
876             Log.d(TAG, "Push Header");
877         }
878         if (D) {
879             Log.d(TAG, reply.toString());
880         }
881 
882         int pushResult = ResponseCodes.OBEX_HTTP_OK;
883         try {
884             op.sendHeaders(reply);
885             outputStream = op.openOutputStream();
886             outputStream.flush();
887         } catch (IOException e) {
888             Log.e(TAG, e.toString());
889             pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
890         } finally {
891             if (!closeStream(outputStream, op)) {
892                 pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
893             }
894         }
895         return pushResult;
896     }
897 
898     /** Function to send vcard data to client */
pushBytes(Operation op, final String vcardString)899     private int pushBytes(Operation op, final String vcardString) {
900         if (vcardString == null) {
901             Log.w(TAG, "vcardString is null!");
902             return ResponseCodes.OBEX_HTTP_OK;
903         }
904 
905         OutputStream outputStream = null;
906         int pushResult = ResponseCodes.OBEX_HTTP_OK;
907         try {
908             outputStream = op.openOutputStream();
909             outputStream.write(vcardString.getBytes());
910             if (V) {
911                 Log.v(TAG, "Send Data complete!");
912             }
913         } catch (IOException e) {
914             Log.e(TAG, "open/write outputstrem failed" + e.toString());
915             pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
916         }
917 
918         if (!closeStream(outputStream, op)) {
919             pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
920         }
921 
922         return pushResult;
923     }
924 
handleAppParaForResponse(AppParamValue appParamValue, int size, HeaderSet reply, Operation op, String name)925     private int handleAppParaForResponse(AppParamValue appParamValue, int size, HeaderSet reply,
926             Operation op, String name) {
927         byte[] misnum = new byte[1];
928         ApplicationParameter ap = new ApplicationParameter();
929         boolean needSendCallHistoryVersionCounters = false;
930         if (isNameMatchTarget(name, MCH) || isNameMatchTarget(name, ICH) || isNameMatchTarget(name,
931                 OCH) || isNameMatchTarget(name, CCH)) {
932             needSendCallHistoryVersionCounters =
933                     checkPbapFeatureSupport(mFolderVersionCounterbitMask);
934         }
935         boolean needSendPhonebookVersionCounters = false;
936         if (isNameMatchTarget(name, PB)) {
937             needSendPhonebookVersionCounters =
938                     checkPbapFeatureSupport(mFolderVersionCounterbitMask);
939         }
940 
941         // In such case, PCE only want the number of index.
942         // So response not contain any Body header.
943         if (mNeedPhonebookSize) {
944             if (D) {
945                 Log.d(TAG, "Need Phonebook size in response header.");
946             }
947             mNeedPhonebookSize = false;
948 
949             byte[] pbsize = new byte[2];
950 
951             pbsize[0] = (byte) ((size / 256) & 0xff); // HIGH VALUE
952             pbsize[1] = (byte) ((size % 256) & 0xff); // LOW VALUE
953             ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
954                     ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
955 
956             if (mNeedNewMissedCallsNum) {
957                 mNeedNewMissedCallsNum = false;
958                 int nmnum = 0;
959                 ContentResolver contentResolver;
960                 contentResolver = mContext.getContentResolver();
961 
962                 Cursor c = contentResolver.query(Calls.CONTENT_URI, null,
963                         Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND "
964                                 + android.provider.CallLog.Calls.NEW + " = 1", null,
965                         Calls.DEFAULT_SORT_ORDER);
966 
967                 if (c != null) {
968                     nmnum = c.getCount();
969                     c.close();
970                 }
971 
972                 nmnum = nmnum > 0 ? nmnum : 0;
973                 misnum[0] = (byte) nmnum;
974                 if (D) {
975                     Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
976                             + nmnum);
977                 }
978             }
979 
980             if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
981                 setDbCounters(ap);
982             }
983             if (needSendPhonebookVersionCounters) {
984                 setFolderVersionCounters(ap);
985             }
986             if (needSendCallHistoryVersionCounters) {
987                 setCallversionCounters(ap, appParamValue);
988             }
989             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
990 
991             if (D) {
992                 Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
993             }
994 
995             return pushHeader(op, reply);
996         }
997 
998         // Only apply to "mch" download/listing.
999         // NewMissedCalls is used only in the response, together with Body
1000         // header.
1001         if (mNeedNewMissedCallsNum) {
1002             if (D) {
1003                 Log.d(TAG, "Need new missed call num in response header.");
1004             }
1005             mNeedNewMissedCallsNum = false;
1006             int nmnum = 0;
1007             ContentResolver contentResolver;
1008             contentResolver = mContext.getContentResolver();
1009 
1010             Cursor c = contentResolver.query(Calls.CONTENT_URI, null,
1011                     Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND "
1012                             + android.provider.CallLog.Calls.NEW + " = 1", null,
1013                     Calls.DEFAULT_SORT_ORDER);
1014 
1015             if (c != null) {
1016                 nmnum = c.getCount();
1017                 c.close();
1018             }
1019 
1020             nmnum = nmnum > 0 ? nmnum : 0;
1021             misnum[0] = (byte) nmnum;
1022             if (D) {
1023                 Log.d(TAG,
1024                         "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
1025             }
1026 
1027             ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
1028                     ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
1029             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1030             if (D) {
1031                 Log.d(TAG,
1032                         "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
1033             }
1034 
1035             // Only Specifies the headers, not write for now, will write to PCE
1036             // together with Body
1037             try {
1038                 op.sendHeaders(reply);
1039             } catch (IOException e) {
1040                 Log.e(TAG, e.toString());
1041                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
1042             }
1043         }
1044 
1045         if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
1046             setDbCounters(ap);
1047             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1048             try {
1049                 op.sendHeaders(reply);
1050             } catch (IOException e) {
1051                 Log.e(TAG, e.toString());
1052                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
1053             }
1054         }
1055 
1056         if (needSendPhonebookVersionCounters) {
1057             setFolderVersionCounters(ap);
1058             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1059             try {
1060                 op.sendHeaders(reply);
1061             } catch (IOException e) {
1062                 Log.e(TAG, e.toString());
1063                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
1064             }
1065         }
1066 
1067         if (needSendCallHistoryVersionCounters) {
1068             setCallversionCounters(ap, appParamValue);
1069             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
1070             try {
1071                 op.sendHeaders(reply);
1072             } catch (IOException e) {
1073                 Log.e(TAG, e.toString());
1074                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
1075             }
1076         }
1077 
1078         return NEED_SEND_BODY;
1079     }
1080 
pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, String name)1081     private int pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
1082             Operation op, String name) {
1083         String searchAttr = appParamValue.searchAttr.trim();
1084 
1085         if (searchAttr == null || searchAttr.length() == 0) {
1086             // If searchAttr is not set by PCE, set default value per spec.
1087             appParamValue.searchAttr = "0";
1088             if (D) {
1089                 Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
1090             }
1091         } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
1092             Log.w(TAG, "search attr not supported");
1093             if (searchAttr.equals("2")) {
1094                 // search by sound is not supported currently
1095                 Log.w(TAG, "do not support search by sound");
1096                 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1097             }
1098             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1099         } else {
1100             Log.i(TAG, "searchAttr is valid: " + searchAttr);
1101         }
1102 
1103         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1104         int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1105         if (needSendBody != NEED_SEND_BODY) {
1106             op.noBodyHeader();
1107             return needSendBody;
1108         }
1109 
1110         if (size == 0) {
1111             if (D) {
1112                 Log.d(TAG, "PhonebookSize is 0, return.");
1113             }
1114             return ResponseCodes.OBEX_HTTP_OK;
1115         }
1116 
1117         String orderPara = appParamValue.order.trim();
1118         if (TextUtils.isEmpty(orderPara)) {
1119             // If order parameter is not set by PCE, set default value per spec.
1120             orderPara = "0";
1121             if (D) {
1122                 Log.d(TAG, "Order parameter is not set by PCE. "
1123                         + "Assume order by 'Indexed' by default");
1124             }
1125         } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
1126             if (D) {
1127                 Log.d(TAG, "Order parameter is not supported: " + appParamValue.order);
1128             }
1129             if (orderPara.equals("2")) {
1130                 // Order by sound is not supported currently
1131                 Log.w(TAG, "Do not support order by sound");
1132                 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
1133             }
1134             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1135         } else {
1136             Log.i(TAG, "Order parameter is valid: " + orderPara);
1137         }
1138 
1139         if (orderPara.equals("0")) {
1140             mOrderBy = ORDER_BY_INDEXED;
1141         } else if (orderPara.equals("1")) {
1142             mOrderBy = ORDER_BY_ALPHABETICAL;
1143         }
1144 
1145         return sendVcardListingXml(appParamValue, op, needSendBody, size);
1146     }
1147 
pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op, HeaderSet reply, final String name, final String currentPath)1148     private int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op,
1149             HeaderSet reply, final String name, final String currentPath) {
1150         if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
1151             if (D) {
1152                 Log.d(TAG, "Name is Null, or the length of name < 5 !");
1153             }
1154             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1155         }
1156         String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
1157         int intIndex = 0;
1158         if (strIndex.trim().length() != 0) {
1159             try {
1160                 intIndex = Integer.parseInt(strIndex);
1161             } catch (NumberFormatException e) {
1162                 Log.e(TAG, "catch number format exception " + e.toString());
1163                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1164             }
1165         }
1166 
1167         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
1168         int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
1169         if (size == 0) {
1170             if (D) {
1171                 Log.d(TAG, "PhonebookSize is 0, return.");
1172             }
1173             return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1174         }
1175 
1176         boolean vcard21 = appParamValue.vcard21;
1177         if (appParamValue.needTag == 0) {
1178             Log.w(TAG, "wrong path!");
1179             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1180         } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
1181             if (intIndex < 0 || intIndex >= size) {
1182                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1183                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1184             } else if (intIndex == 0) {
1185                 // For PB_PATH, 0.vcf is the phone number of this phone.
1186                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,
1187                         appParamValue.ignorefilter ? null : appParamValue.propertySelector);
1188                 return pushBytes(op, ownerVcard);
1189             } else {
1190                 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
1191                         mOrderBy, appParamValue.ignorefilter, appParamValue.propertySelector);
1192             }
1193         } else {
1194             if (intIndex <= 0 || intIndex > size) {
1195                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
1196                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
1197             }
1198             // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
1199             // begin from 1.vcf
1200             if (intIndex >= 1) {
1201                 return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1202                         intIndex, intIndex, vcard21, needSendBody, size, appParamValue.ignorefilter,
1203                         appParamValue.propertySelector, appParamValue.vCardSelector,
1204                         appParamValue.vCardSelectorOperator, mVcardSelector);
1205             }
1206         }
1207         return ResponseCodes.OBEX_HTTP_OK;
1208     }
1209 
pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply, Operation op, final String name)1210     private int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
1211             Operation op, final String name) {
1212         // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
1213         if (name != null) {
1214             int dotIndex = name.indexOf(".");
1215             String vcf = "vcf";
1216             if (dotIndex >= 0 && dotIndex <= name.length()) {
1217                 if (!name.regionMatches(dotIndex + 1, vcf, 0, vcf.length())) {
1218                     Log.w(TAG, "name is not .vcf");
1219                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
1220                 }
1221             }
1222         } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
1223 
1224         int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
1225         int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op, name);
1226         if (needSendBody != NEED_SEND_BODY) {
1227             op.noBodyHeader();
1228             return needSendBody;
1229         }
1230 
1231         if (pbSize == 0) {
1232             if (D) {
1233                 Log.d(TAG, "PhonebookSize is 0, return.");
1234             }
1235             return ResponseCodes.OBEX_HTTP_OK;
1236         }
1237 
1238         int requestSize =
1239                 pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
1240         int startPoint = appParamValue.listStartOffset;
1241         if (startPoint < 0 || startPoint >= pbSize) {
1242             Log.w(TAG, "listStartOffset is not correct! " + startPoint);
1243             return ResponseCodes.OBEX_HTTP_OK;
1244         }
1245 
1246         // Limit the number of call log to CALLLOG_NUM_LIMIT
1247         if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1248             if (requestSize > CALLLOG_NUM_LIMIT) {
1249                 requestSize = CALLLOG_NUM_LIMIT;
1250             }
1251         }
1252 
1253         int endPoint = startPoint + requestSize - 1;
1254         if (endPoint > pbSize - 1) {
1255             endPoint = pbSize - 1;
1256         }
1257         if (D) {
1258             Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + startPoint
1259                     + " endPoint=" + endPoint);
1260         }
1261 
1262         boolean vcard21 = appParamValue.vcard21;
1263         if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
1264             if (startPoint == 0) {
1265                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,
1266                         appParamValue.ignorefilter ? null : appParamValue.propertySelector);
1267                 if (endPoint == 0) {
1268                     return pushBytes(op, ownerVcard);
1269                 } else {
1270                     return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
1271                             ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter,
1272                             appParamValue.propertySelector, appParamValue.vCardSelector,
1273                             appParamValue.vCardSelectorOperator, mVcardSelector);
1274                 }
1275             } else {
1276                 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
1277                         vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter,
1278                         appParamValue.propertySelector, appParamValue.vCardSelector,
1279                         appParamValue.vCardSelectorOperator, mVcardSelector);
1280             }
1281         } else {
1282             return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
1283                     startPoint + 1, endPoint + 1, vcard21, needSendBody, pbSize,
1284                     appParamValue.ignorefilter, appParamValue.propertySelector,
1285                     appParamValue.vCardSelector, appParamValue.vCardSelectorOperator,
1286                     mVcardSelector);
1287         }
1288     }
1289 
closeStream(final OutputStream out, final Operation op)1290     public static boolean closeStream(final OutputStream out, final Operation op) {
1291         boolean returnvalue = true;
1292         try {
1293             if (out != null) {
1294                 out.close();
1295             }
1296         } catch (IOException e) {
1297             Log.e(TAG, "outputStream close failed" + e.toString());
1298             returnvalue = false;
1299         }
1300         try {
1301             if (op != null) {
1302                 op.close();
1303             }
1304         } catch (IOException e) {
1305             Log.e(TAG, "operation close failed" + e.toString());
1306             returnvalue = false;
1307         }
1308         return returnvalue;
1309     }
1310 
1311     // Reserved for future use. In case PSE challenge PCE and PCE input wrong
1312     // session key.
1313     @Override
onAuthenticationFailure(final byte[] userName)1314     public final void onAuthenticationFailure(final byte[] userName) {
1315     }
1316 
createSelectionPara(final int type)1317     public static final String createSelectionPara(final int type) {
1318         String selection = null;
1319         switch (type) {
1320             case ContentType.INCOMING_CALL_HISTORY:
1321                 selection =
1322                         "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR " + Calls.TYPE
1323                                 + "=" + CallLog.Calls.REJECTED_TYPE + ")";
1324                 break;
1325             case ContentType.OUTGOING_CALL_HISTORY:
1326                 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
1327                 break;
1328             case ContentType.MISSED_CALL_HISTORY:
1329                 selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
1330                 break;
1331             default:
1332                 break;
1333         }
1334         if (V) {
1335             Log.v(TAG, "Call log selection: " + selection);
1336         }
1337         return selection;
1338     }
1339 
1340     /**
1341      * XML encode special characters in the name field
1342      */
xmlEncode(String name, StringBuilder result)1343     private void xmlEncode(String name, StringBuilder result) {
1344         if (name == null) {
1345             return;
1346         }
1347 
1348         final StringCharacterIterator iterator = new StringCharacterIterator(name);
1349         char character = iterator.current();
1350         while (character != CharacterIterator.DONE) {
1351             if (character == '<') {
1352                 result.append("&lt;");
1353             } else if (character == '>') {
1354                 result.append("&gt;");
1355             } else if (character == '\"') {
1356                 result.append("&quot;");
1357             } else if (character == '\'') {
1358                 result.append("&#039;");
1359             } else if (character == '&') {
1360                 result.append("&amp;");
1361             } else {
1362                 // The char is not a special one, add it to the result as is
1363                 result.append(character);
1364             }
1365             character = iterator.next();
1366         }
1367     }
1368 
writeVCardEntry(int vcfIndex, String name, StringBuilder result)1369     private void writeVCardEntry(int vcfIndex, String name, StringBuilder result) {
1370         result.append("<card handle=\"");
1371         result.append(vcfIndex);
1372         result.append(".vcf\" name=\"");
1373         xmlEncode(name, result);
1374         result.append("\"/>");
1375     }
1376 
notifyUpdateWakeLock()1377     private void notifyUpdateWakeLock() {
1378         Message msg = Message.obtain(mCallback);
1379         msg.what = BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK;
1380         msg.sendToTarget();
1381     }
1382 
logHeader(HeaderSet hs)1383     public static final void logHeader(HeaderSet hs) {
1384         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1385         try {
1386 
1387             Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1388             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1389             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1390             Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1391             Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1392             Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1393             Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1394             Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1395             Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1396             Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1397             Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1398             Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1399         } catch (IOException e) {
1400             Log.e(TAG, "dump HeaderSet error " + e);
1401         }
1402     }
1403 
setDbCounters(ApplicationParameter ap)1404     private void setDbCounters(ApplicationParameter ap) {
1405         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.DATABASEIDENTIFIER_TAGID,
1406                 ApplicationParameter.TRIPLET_LENGTH.DATABASEIDENTIFIER_LENGTH,
1407                 getDatabaseIdentifier());
1408     }
1409 
setFolderVersionCounters(ApplicationParameter ap)1410     private void setFolderVersionCounters(ApplicationParameter ap) {
1411         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1412                 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1413                 getPBPrimaryFolderVersion());
1414         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1415                 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1416                 getPBSecondaryFolderVersion());
1417     }
1418 
setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue)1419     private void setCallversionCounters(ApplicationParameter ap, AppParamValue appParamValue) {
1420         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PRIMARYVERSIONCOUNTER_TAGID,
1421                 ApplicationParameter.TRIPLET_LENGTH.PRIMARYVERSIONCOUNTER_LENGTH,
1422                 appParamValue.callHistoryVersionCounter);
1423 
1424         ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.SECONDARYVERSIONCOUNTER_TAGID,
1425                 ApplicationParameter.TRIPLET_LENGTH.SECONDARYVERSIONCOUNTER_LENGTH,
1426                 appParamValue.callHistoryVersionCounter);
1427     }
1428 
getDatabaseIdentifier()1429     private byte[] getDatabaseIdentifier() {
1430         mDatabaseIdentifierHigh = 0;
1431         mDatabaseIdentifierLow = BluetoothPbapUtils.sDbIdentifier.get();
1432         if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
1433                 && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
1434             ByteBuffer ret = ByteBuffer.allocate(16);
1435             ret.putLong(mDatabaseIdentifierHigh);
1436             ret.putLong(mDatabaseIdentifierLow);
1437             return ret.array();
1438         } else {
1439             return null;
1440         }
1441     }
1442 
getPBPrimaryFolderVersion()1443     private byte[] getPBPrimaryFolderVersion() {
1444         long primaryVcMsb = 0;
1445         ByteBuffer pvc = ByteBuffer.allocate(16);
1446         pvc.putLong(primaryVcMsb);
1447 
1448         Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter);
1449         pvc.putLong(BluetoothPbapUtils.sPrimaryVersionCounter);
1450         return pvc.array();
1451     }
1452 
getPBSecondaryFolderVersion()1453     private byte[] getPBSecondaryFolderVersion() {
1454         long secondaryVcMsb = 0;
1455         ByteBuffer svc = ByteBuffer.allocate(16);
1456         svc.putLong(secondaryVcMsb);
1457 
1458         Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.sSecondaryVersionCounter);
1459         svc.putLong(BluetoothPbapUtils.sSecondaryVersionCounter);
1460         return svc.array();
1461     }
1462 
checkPbapFeatureSupport(long featureBitMask)1463     private boolean checkPbapFeatureSupport(long featureBitMask) {
1464         Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask);
1465         return ((ByteBuffer.wrap(mConnAppParamValue.supportedFeature).getInt() & featureBitMask)
1466                 != 0);
1467     }
1468 }
1469