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