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