1 /* 2 * Copyright (C) 2016 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.pbapclient; 18 19 import android.util.Log; 20 21 import com.android.vcard.VCardConfig; 22 import com.android.vcard.VCardEntry; 23 import com.android.vcard.VCardEntryConstructor; 24 import com.android.vcard.VCardEntryHandler; 25 import com.android.vcard.VCardParser; 26 import com.android.vcard.VCardParser_V21; 27 import com.android.vcard.VCardParser_V30; 28 import com.android.vcard.exception.VCardException; 29 import com.android.vcard.exception.VCardVersionException; 30 31 import java.io.BufferedInputStream; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.util.ArrayList; 35 import java.util.List; 36 37 public class PbapPhonebook { 38 private static final String TAG = PbapPhonebook.class.getSimpleName(); 39 40 // {@link BufferedInputStream#DEFAULT_BUFFER_SIZE} is not public 41 private static final int BIS_DEFAULT_BUFFER_SIZE = 8192; 42 43 // Phonebooks, including call history. See PBAP 1.2.3, Section 3.1.2 44 public static final String LOCAL_PHONEBOOK_PATH = "telecom/pb.vcf"; // Device phonebook 45 public static final String FAVORITES_PATH = "telecom/fav.vcf"; // Contacts marked as favorite 46 public static final String MCH_PATH = "telecom/mch.vcf"; // Missed Calls 47 public static final String ICH_PATH = "telecom/ich.vcf"; // Incoming Calls 48 public static final String OCH_PATH = "telecom/och.vcf"; // Outgoing Calls 49 public static final String SIM_PHONEBOOK_PATH = "SIM1/telecom/pb.vcf"; // SIM stored phonebook 50 public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf"; // SIM stored Missed Calls 51 public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf"; // SIM stored Incoming Calls 52 public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf"; // SIM stored Outgoing Calls 53 54 // VCard Formats, both are required to be supported by the Server, PBAP 1.2.3, Section 5.1.4.2 55 public static byte FORMAT_VCARD_21 = 0; 56 public static byte FORMAT_VCARD_30 = 1; 57 58 private final String mPhonebook; 59 private final int mListStartOffset; 60 private final List<VCardEntry> mCards = new ArrayList<VCardEntry>(); 61 62 class CardEntryHandler implements VCardEntryHandler { 63 @Override onStart()64 public void onStart() {} 65 66 @Override onEntryCreated(VCardEntry entry)67 public void onEntryCreated(VCardEntry entry) { 68 mCards.add(entry); 69 } 70 71 @Override onEnd()72 public void onEnd() {} 73 } 74 PbapPhonebook(String phonebook)75 PbapPhonebook(String phonebook) { 76 mPhonebook = phonebook; 77 mListStartOffset = 0; 78 } 79 PbapPhonebook( String phonebook, byte format, int listStartOffset, InputStream inputStream)80 PbapPhonebook( 81 String phonebook, 82 byte format, 83 int listStartOffset, 84 InputStream inputStream) 85 throws IOException { 86 if (format != FORMAT_VCARD_21 && format != FORMAT_VCARD_30) { 87 throw new IllegalArgumentException("Unsupported vCard version."); 88 } 89 mPhonebook = phonebook; 90 mListStartOffset = listStartOffset; 91 parse(inputStream, format); 92 } 93 parse(InputStream in, byte format)94 private void parse(InputStream in, byte format) throws IOException { 95 VCardParser parser; 96 97 if (format == FORMAT_VCARD_30) { 98 parser = new VCardParser_V30(); 99 } else { 100 parser = new VCardParser_V21(); 101 } 102 103 VCardEntryConstructor constructor = 104 new VCardEntryConstructor(VCardConfig.VCARD_TYPE_V21_GENERIC); 105 106 CardEntryHandler handler = new CardEntryHandler(); 107 constructor.addEntryHandler(handler); 108 109 parser.addInterpreter(constructor); 110 111 // {@link BufferedInputStream} supports the {@link InputStream#mark} and 112 // {@link InputStream#reset} methods. 113 BufferedInputStream bufferedInput = new BufferedInputStream(in); 114 bufferedInput.mark(BIS_DEFAULT_BUFFER_SIZE /* readlimit */); 115 116 // If there is a {@link VCardVersionException}, try parsing again with a different 117 // version. Otherwise, parsing either succeeds (i.e., no {@link VCardException}) or it 118 // fails with a different {@link VCardException}. 119 if (parsedWithVcardVersionException(parser, bufferedInput)) { 120 // PBAP v1.2.3 only supports vCard versions 2.1 and 3.0; it's one or the other 121 if (format == FORMAT_VCARD_21) { 122 parser = new VCardParser_V30(); 123 Log.w(TAG, "vCard version and Parser mismatch; expected v2.1, switching to v3.0"); 124 } else { 125 parser = new VCardParser_V21(); 126 Log.w(TAG, "vCard version and Parser mismatch; expected v3.0, switching to v2.1"); 127 } 128 // reset and try again 129 bufferedInput.reset(); 130 mCards.clear(); 131 constructor.clear(); 132 parser.addInterpreter(constructor); 133 if (parsedWithVcardVersionException(parser, bufferedInput)) { 134 Log.e(TAG, "unsupported vCard version, neither v2.1 nor v3.0"); 135 } 136 } 137 } 138 139 /** 140 * Attempts to parse, with an eye on whether the correct version of Parser is used. 141 * 142 * @param parser -- the {@link VCardParser} to use. 143 * @param in -- the {@link InputStream} to parse. 144 * @return {@code true} if there was a {@link VCardVersionException}; {@code false} if there is 145 * any other {@link VCardException} or succeeds (i.e., no {@link VCardException}). 146 * @throws IOException if there's an issue reading the {@link InputStream}. 147 */ parsedWithVcardVersionException(VCardParser parser, InputStream in)148 private static boolean parsedWithVcardVersionException(VCardParser parser, InputStream in) 149 throws IOException { 150 try { 151 parser.parse(in); 152 } catch (VCardVersionException e1) { 153 Log.w(TAG, "vCard version and Parser mismatch", e1); 154 return true; 155 } catch (VCardException e2) { 156 Log.e(TAG, "vCard exception", e2); 157 } 158 return false; 159 } 160 161 /** 162 * Get the phonebook path associated with this PbapPhonebook object 163 * 164 * @return a string representing the path these VCard objects were requested from 165 */ getPhonebook()166 public String getPhonebook() { 167 return mPhonebook; 168 } 169 170 /** 171 * Get the offset associated with this PbapPhonebook object 172 * 173 * <p>The offset represents the start index of the remote contacts pull 174 * 175 * @return an int representing the offset index where this pull started from 176 */ getOffset()177 public int getOffset() { 178 return mListStartOffset; 179 } 180 181 /** 182 * Get the total number of contacts contained in this phonebook 183 * 184 * @return an int containing the total number of contacts contained in this phonebook 185 */ getCount()186 public int getCount() { 187 return mCards.size(); 188 } 189 190 /** 191 * Get the list of VCard objects contained in this phonebook 192 * 193 * @return a list of VCard objects in this phonebook 194 */ getList()195 public List<VCardEntry> getList() { 196 return mCards; 197 } 198 199 @Override toString()200 public String toString() { 201 return "<" + TAG + "phonebook=" + mPhonebook + " entries=" + getCount() + ">"; 202 } 203 } 204