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