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