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