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