1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.bluetooth.pbap; 17 18 import com.android.bluetooth.R; 19 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.database.sqlite.SQLiteException; 24 import android.net.Uri; 25 import android.provider.CallLog; 26 import android.provider.CallLog.Calls; 27 import android.text.TextUtils; 28 import android.text.format.Time; 29 import android.util.Log; 30 31 import com.android.vcard.VCardBuilder; 32 import com.android.vcard.VCardConfig; 33 import com.android.vcard.VCardConstants; 34 import com.android.vcard.VCardUtils; 35 36 import java.util.Arrays; 37 38 /** 39 * VCard composer especially for Call Log used in Bluetooth. 40 */ 41 public class BluetoothPbapCallLogComposer { 42 private static final String TAG = "CallLogComposer"; 43 44 private static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO = 45 "Failed to get database information"; 46 47 private static final String FAILURE_REASON_NO_ENTRY = 48 "There's no exportable in the database"; 49 50 private static final String FAILURE_REASON_NOT_INITIALIZED = 51 "The vCard composer object is not correctly initialized"; 52 53 /** Should be visible only from developers... (no need to translate, hopefully) */ 54 private static final String FAILURE_REASON_UNSUPPORTED_URI = 55 "The Uri vCard composer received is not supported by the composer."; 56 57 private static final String NO_ERROR = "No error"; 58 59 /** The projection to use when querying the call log table */ 60 private static final String[] sCallLogProjection = new String[] { 61 Calls.NUMBER, Calls.DATE, Calls.TYPE, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE, 62 Calls.CACHED_NUMBER_LABEL, Calls.NUMBER_PRESENTATION 63 }; 64 private static final int NUMBER_COLUMN_INDEX = 0; 65 private static final int DATE_COLUMN_INDEX = 1; 66 private static final int CALL_TYPE_COLUMN_INDEX = 2; 67 private static final int CALLER_NAME_COLUMN_INDEX = 3; 68 private static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 4; 69 private static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 5; 70 private static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6; 71 72 // Property for call log entry 73 private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME"; 74 private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "RECEIVED"; 75 private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "DIALED"; 76 private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED"; 77 78 private final Context mContext; 79 private ContentResolver mContentResolver; 80 private Cursor mCursor; 81 82 private boolean mTerminateIsCalled; 83 84 private String mErrorReason = NO_ERROR; 85 86 public BluetoothPbapCallLogComposer(final Context context) { 87 mContext = context; 88 mContentResolver = context.getContentResolver(); 89 } 90 91 public boolean init(final Uri contentUri, final String selection, 92 final String[] selectionArgs, final String sortOrder) { 93 final String[] projection; 94 if (CallLog.Calls.CONTENT_URI.equals(contentUri)) { 95 projection = sCallLogProjection; 96 } else { 97 mErrorReason = FAILURE_REASON_UNSUPPORTED_URI; 98 return false; 99 } 100 101 mCursor = mContentResolver.query( 102 contentUri, projection, selection, selectionArgs, sortOrder); 103 104 if (mCursor == null) { 105 mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO; 106 return false; 107 } 108 109 if (mCursor.getCount() == 0 || !mCursor.moveToFirst()) { 110 try { 111 mCursor.close(); 112 } catch (SQLiteException e) { 113 Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage()); 114 } finally { 115 mErrorReason = FAILURE_REASON_NO_ENTRY; 116 mCursor = null; 117 } 118 return false; 119 } 120 121 return true; 122 } 123 124 public String createOneEntry(boolean vcardVer21) { 125 if (mCursor == null || mCursor.isAfterLast()) { 126 mErrorReason = FAILURE_REASON_NOT_INITIALIZED; 127 return null; 128 } 129 try { 130 return createOneCallLogEntryInternal(vcardVer21); 131 } finally { 132 mCursor.moveToNext(); 133 } 134 } 135 136 private String createOneCallLogEntryInternal(boolean vcardVer21) { 137 final int vcardType = (vcardVer21 ? VCardConfig.VCARD_TYPE_V21_GENERIC : 138 VCardConfig.VCARD_TYPE_V30_GENERIC) | 139 VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING; 140 final VCardBuilder builder = new VCardBuilder(vcardType); 141 String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX); 142 String number = mCursor.getString(NUMBER_COLUMN_INDEX); 143 final int numberPresentation = mCursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX); 144 if (TextUtils.isEmpty(name)) { 145 name = ""; 146 } 147 if (numberPresentation != Calls.PRESENTATION_ALLOWED) { 148 // setting name to "" as FN/N must be empty fields in this case. 149 name = ""; 150 // TODO: there are really 3 possible strings that could be set here: 151 // "unknown", "private", and "payphone". 152 number = mContext.getString(R.string.unknownNumber); 153 } 154 final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name)); 155 builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false); 156 builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false); 157 158 final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX); 159 String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX); 160 if (TextUtils.isEmpty(label)) { 161 label = Integer.toString(type); 162 } 163 builder.appendTelLine(type, label, number, false); 164 tryAppendCallHistoryTimeStampField(builder); 165 166 return builder.toString(); 167 } 168 169 /** 170 * This static function is to compose vCard for phone own number 171 */ 172 public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName, 173 String phoneNumber, boolean vcardVer21) { 174 final int vcardType = (vcardVer21 ? 175 VCardConfig.VCARD_TYPE_V21_GENERIC : 176 VCardConfig.VCARD_TYPE_V30_GENERIC) | 177 VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING; 178 final VCardBuilder builder = new VCardBuilder(vcardType); 179 boolean needCharset = false; 180 if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) { 181 needCharset = true; 182 } 183 builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false); 184 builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false); 185 186 if (!TextUtils.isEmpty(phoneNumber)) { 187 String label = Integer.toString(phonetype); 188 builder.appendTelLine(phonetype, label, phoneNumber, false); 189 } 190 191 return builder.toString(); 192 } 193 194 /** 195 * Format according to RFC 2445 DATETIME type. 196 * The format is: ("%Y%m%dT%H%M%S"). 197 */ 198 private final String toRfc2455Format(final long millSecs) { 199 Time startDate = new Time(); 200 startDate.set(millSecs); 201 return startDate.format2445(); 202 } 203 204 /** 205 * Try to append the property line for a call history time stamp field if possible. 206 * Do nothing if the call log type gotton from the database is invalid. 207 */ 208 private void tryAppendCallHistoryTimeStampField(final VCardBuilder builder) { 209 // Extension for call history as defined in 210 // in the Specification for Ic Mobile Communcation - ver 1.1, 211 // Oct 2000. This is used to send the details of the call 212 // history - missed, incoming, outgoing along with date and time 213 // to the requesting device (For example, transferring phone book 214 // when connected over bluetooth) 215 // 216 // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000" 217 final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX); 218 final String callLogTypeStr; 219 switch (callLogType) { 220 case Calls.INCOMING_TYPE: { 221 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING; 222 break; 223 } 224 case Calls.OUTGOING_TYPE: { 225 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING; 226 break; 227 } 228 case Calls.MISSED_TYPE: { 229 callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED; 230 break; 231 } 232 default: { 233 Log.w(TAG, "Call log type not correct."); 234 return; 235 } 236 } 237 238 final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX); 239 builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP, 240 Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong)); 241 } 242 243 public void terminate() { 244 if (mCursor != null) { 245 try { 246 mCursor.close(); 247 } catch (SQLiteException e) { 248 Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage()); 249 } 250 mCursor = null; 251 } 252 253 mTerminateIsCalled = true; 254 } 255 256 @Override 257 public void finalize() { 258 if (!mTerminateIsCalled) { 259 terminate(); 260 } 261 } 262 263 public int getCount() { 264 if (mCursor == null) { 265 return 0; 266 } 267 return mCursor.getCount(); 268 } 269 270 public boolean isAfterLast() { 271 if (mCursor == null) { 272 return false; 273 } 274 return mCursor.isAfterLast(); 275 } 276 277 public String getErrorReason() { 278 return mErrorReason; 279 } 280 } 281