| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.android.bluetooth.pbap; |
| |
| import com.android.bluetooth.R; |
| import com.android.internal.telephony.CallerInfo; |
| |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.database.sqlite.SQLiteException; |
| import android.net.Uri; |
| import android.provider.CallLog; |
| import android.provider.CallLog.Calls; |
| import android.text.TextUtils; |
| import android.text.format.Time; |
| import android.util.Log; |
| |
| import com.android.vcard.VCardBuilder; |
| import com.android.vcard.VCardConfig; |
| import com.android.vcard.VCardConstants; |
| import com.android.vcard.VCardUtils; |
| |
| import java.util.Arrays; |
| |
| /** |
| * VCard composer especially for Call Log used in Bluetooth. |
| */ |
| public class BluetoothPbapCallLogComposer { |
| private static final String TAG = "CallLogComposer"; |
| |
| private static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO = |
| "Failed to get database information"; |
| |
| private static final String FAILURE_REASON_NO_ENTRY = |
| "There's no exportable in the database"; |
| |
| private static final String FAILURE_REASON_NOT_INITIALIZED = |
| "The vCard composer object is not correctly initialized"; |
| |
| /** Should be visible only from developers... (no need to translate, hopefully) */ |
| private static final String FAILURE_REASON_UNSUPPORTED_URI = |
| "The Uri vCard composer received is not supported by the composer."; |
| |
| private static final String NO_ERROR = "No error"; |
| |
| /** The projection to use when querying the call log table */ |
| private static final String[] sCallLogProjection = new String[] { |
| Calls.NUMBER, Calls.DATE, Calls.TYPE, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE, |
| Calls.CACHED_NUMBER_LABEL |
| }; |
| private static final int NUMBER_COLUMN_INDEX = 0; |
| private static final int DATE_COLUMN_INDEX = 1; |
| private static final int CALL_TYPE_COLUMN_INDEX = 2; |
| private static final int CALLER_NAME_COLUMN_INDEX = 3; |
| private static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 4; |
| private static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 5; |
| |
| // Property for call log entry |
| private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME"; |
| private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "RECEIVED"; |
| private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "DIALED"; |
| private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED"; |
| |
| private final Context mContext; |
| private ContentResolver mContentResolver; |
| private Cursor mCursor; |
| |
| private boolean mTerminateIsCalled; |
| |
| private String mErrorReason = NO_ERROR; |
| |
| public BluetoothPbapCallLogComposer(final Context context) { |
| mContext = context; |
| mContentResolver = context.getContentResolver(); |
| } |
| |
| public boolean init(final Uri contentUri, final String selection, |
| final String[] selectionArgs, final String sortOrder) { |
| final String[] projection; |
| if (CallLog.Calls.CONTENT_URI.equals(contentUri)) { |
| projection = sCallLogProjection; |
| } else { |
| mErrorReason = FAILURE_REASON_UNSUPPORTED_URI; |
| return false; |
| } |
| |
| mCursor = mContentResolver.query( |
| contentUri, projection, selection, selectionArgs, sortOrder); |
| |
| if (mCursor == null) { |
| mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO; |
| return false; |
| } |
| |
| if (mCursor.getCount() == 0 || !mCursor.moveToFirst()) { |
| try { |
| mCursor.close(); |
| } catch (SQLiteException e) { |
| Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage()); |
| } finally { |
| mErrorReason = FAILURE_REASON_NO_ENTRY; |
| mCursor = null; |
| } |
| return false; |
| } |
| |
| return true; |
| } |
| |
| public String createOneEntry(boolean vcardVer21) { |
| if (mCursor == null || mCursor.isAfterLast()) { |
| mErrorReason = FAILURE_REASON_NOT_INITIALIZED; |
| return null; |
| } |
| try { |
| return createOneCallLogEntryInternal(vcardVer21); |
| } finally { |
| mCursor.moveToNext(); |
| } |
| } |
| |
| private String createOneCallLogEntryInternal(boolean vcardVer21) { |
| final int vcardType = (vcardVer21 ? VCardConfig.VCARD_TYPE_V21_GENERIC : |
| VCardConfig.VCARD_TYPE_V30_GENERIC) | |
| VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING; |
| final VCardBuilder builder = new VCardBuilder(vcardType); |
| String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX); |
| if (TextUtils.isEmpty(name)) { |
| name = ""; |
| } |
| if (CallerInfo.UNKNOWN_NUMBER.equals(name) || CallerInfo.PRIVATE_NUMBER.equals(name) || |
| CallerInfo.PAYPHONE_NUMBER.equals(name)) { |
| // setting name to "" as FN/N must be empty fields in this case. |
| name = ""; |
| } |
| final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name)); |
| builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false); |
| builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false); |
| |
| String number = mCursor.getString(NUMBER_COLUMN_INDEX); |
| if (CallerInfo.UNKNOWN_NUMBER.equals(number) || |
| CallerInfo.PRIVATE_NUMBER.equals(number) || |
| CallerInfo.PAYPHONE_NUMBER.equals(number)) { |
| number = mContext.getString(R.string.unknownNumber); |
| } |
| final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX); |
| String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX); |
| if (TextUtils.isEmpty(label)) { |
| label = Integer.toString(type); |
| } |
| builder.appendTelLine(type, label, number, false); |
| tryAppendCallHistoryTimeStampField(builder); |
| |
| return builder.toString(); |
| } |
| |
| /** |
| * This static function is to compose vCard for phone own number |
| */ |
| public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName, |
| String phoneNumber, boolean vcardVer21) { |
| final int vcardType = (vcardVer21 ? |
| VCardConfig.VCARD_TYPE_V21_GENERIC : |
| VCardConfig.VCARD_TYPE_V30_GENERIC) | |
| VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING; |
| final VCardBuilder builder = new VCardBuilder(vcardType); |
| boolean needCharset = false; |
| if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) { |
| needCharset = true; |
| } |
| builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false); |
| builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false); |
| |
| if (!TextUtils.isEmpty(phoneNumber)) { |
| String label = Integer.toString(phonetype); |
| builder.appendTelLine(phonetype, label, phoneNumber, false); |
| } |
| |
| return builder.toString(); |
| } |
| |
| /** |
| * Format according to RFC 2445 DATETIME type. |
| * The format is: ("%Y%m%dT%H%M%S"). |
| */ |
| private final String toRfc2455Format(final long millSecs) { |
| Time startDate = new Time(); |
| startDate.set(millSecs); |
| return startDate.format2445(); |
| } |
| |
| /** |
| * Try to append the property line for a call history time stamp field if possible. |
| * Do nothing if the call log type gotton from the database is invalid. |
| */ |
| private void tryAppendCallHistoryTimeStampField(final VCardBuilder builder) { |
| // Extension for call history as defined in |
| // in the Specification for Ic Mobile Communcation - ver 1.1, |
| // Oct 2000. This is used to send the details of the call |
| // history - missed, incoming, outgoing along with date and time |
| // to the requesting device (For example, transferring phone book |
| // when connected over bluetooth) |
| // |
| // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000" |
| final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX); |
| final String callLogTypeStr; |
| switch (callLogType) { |
| case Calls.INCOMING_TYPE: { |
| callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING; |
| break; |
| } |
| case Calls.OUTGOING_TYPE: { |
| callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING; |
| break; |
| } |
| case Calls.MISSED_TYPE: { |
| callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED; |
| break; |
| } |
| default: { |
| Log.w(TAG, "Call log type not correct."); |
| return; |
| } |
| } |
| |
| final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX); |
| builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP, |
| Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong)); |
| } |
| |
| public void terminate() { |
| if (mCursor != null) { |
| try { |
| mCursor.close(); |
| } catch (SQLiteException e) { |
| Log.e(TAG, "SQLiteException on Cursor#close(): " + e.getMessage()); |
| } |
| mCursor = null; |
| } |
| |
| mTerminateIsCalled = true; |
| } |
| |
| @Override |
| public void finalize() { |
| if (!mTerminateIsCalled) { |
| terminate(); |
| } |
| } |
| |
| public int getCount() { |
| if (mCursor == null) { |
| return 0; |
| } |
| return mCursor.getCount(); |
| } |
| |
| public boolean isAfterLast() { |
| if (mCursor == null) { |
| return false; |
| } |
| return mCursor.isAfterLast(); |
| } |
| |
| public String getErrorReason() { |
| return mErrorReason; |
| } |
| } |