From: Daisuke Miyakawa Date: Tue, 23 Feb 2010 06:55:19 +0000 (+0900) Subject: Move Pbap logic to Bluetooth package so that we do not have unnecessary tight coupling. X-Git-Tag: android-7.1.2_r17~1434 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=198e5d109571b27b7c45c30ed3ea42febcb99201;p=android-x86%2Fpackages-apps-Bluetooth.git Move Pbap logic to Bluetooth package so that we do not have unnecessary tight coupling. --- diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java b/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java new file mode 100644 index 00000000..abaf3ecd --- /dev/null +++ b/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java @@ -0,0 +1,322 @@ +/* + * 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 android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.pim.vcard.VCardBuilder; +import android.pim.vcard.VCardConfig; +import android.pim.vcard.VCardConstants; +import android.pim.vcard.VCardUtils; +import android.pim.vcard.VCardComposer.OneEntryHandler; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.text.TextUtils; +import android.text.format.Time; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 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 = "INCOMING"; + private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING"; + private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED"; + + private static final String FLAG_TIMEZONE_UTC = "Z"; + + private final Context mContext; + private ContentResolver mContentResolver; + private Cursor mCursor; + private final boolean mCareHandlerErrors; + + private boolean mTerminateIsCalled; + private final List mHandlerList; + + private String mErrorReason = NO_ERROR; + + public BluetoothPbapCallLogComposer(final Context context, boolean careHandlerErrors) { + mContext = context; + mContentResolver = context.getContentResolver(); + mCareHandlerErrors = careHandlerErrors; + mHandlerList = new ArrayList(); + } + + 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 (mCareHandlerErrors) { + List finishedList = new ArrayList( + mHandlerList.size()); + for (OneEntryHandler handler : mHandlerList) { + if (!handler.onInit(mContext)) { + for (OneEntryHandler finished : finishedList) { + finished.onTerminate(); + } + return false; + } + } + } else { + // Just ignore the false returned from onInit(). + for (OneEntryHandler handler : mHandlerList) { + handler.onInit(mContext); + } + } + + 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 void addHandler(OneEntryHandler handler) { + if (handler != null) { + mHandlerList.add(handler); + } + } + + public boolean createOneEntry() { + if (mCursor == null || mCursor.isAfterLast()) { + mErrorReason = FAILURE_REASON_NOT_INITIALIZED; + return false; + } + + final String vcard; + try { + vcard = createOneCallLogEntryInternal(); + } catch (OutOfMemoryError error) { + Log.e(TAG, "OutOfMemoryError occured. Ignore the entry"); + System.gc(); + return true; + } + + if (mCareHandlerErrors) { + List finishedList = new ArrayList( + mHandlerList.size()); + for (OneEntryHandler handler : mHandlerList) { + if (!handler.onEntryCreated(vcard)) { + return false; + } + } + } else { + for (OneEntryHandler handler : mHandlerList) { + handler.onEntryCreated(vcard); + } + } + + return true; + } + + private String createOneCallLogEntryInternal() { + final VCardBuilder builder = new VCardBuilder(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX); + if (TextUtils.isEmpty(name)) { + name = mCursor.getString(NUMBER_COLUMN_INDEX); + } + final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name)); + builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false); + builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false); + + final String number = mCursor.getString(NUMBER_COLUMN_INDEX); + 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_UTF8 : + VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8); + 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%SZ"). + */ + private final String toRfc2455Format(final long millSecs) { + Time startDate = new Time(); + startDate.set(millSecs); + String date = startDate.format2445(); + return date + FLAG_TIMEZONE_UTC; + } + + /** + * 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:20050320T100000Z" + 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() { + for (OneEntryHandler handler : mHandlerList) { + handler.onTerminate(); + } + + 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; + } +} diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java index 634bef89..f51f284b 100644 --- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java @@ -32,35 +32,31 @@ package com.android.bluetooth.pbap; -import com.android.bluetooth.R; - -import android.net.Uri; -import android.os.Handler; -import android.text.TextUtils; -import android.util.Log; -import android.database.Cursor; import android.content.ContentResolver; import android.content.Context; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.provider.ContactsContract.RawContacts; -import android.provider.ContactsContract.PhoneLookup; +import android.database.Cursor; +import android.net.Uri; import android.pim.vcard.VCardComposer; import android.pim.vcard.VCardConfig; import android.pim.vcard.VCardComposer.OneEntryHandler; -import android.provider.ContactsContract; +import android.provider.CallLog; +import android.provider.CallLog.Calls; import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.text.TextUtils; +import android.util.Log; + +import com.android.bluetooth.R; -import javax.obex.ResponseCodes; -import javax.obex.Operation; import java.io.IOException; import java.io.OutputStream; -import java.io.Writer; import java.util.ArrayList; +import javax.obex.Operation; +import javax.obex.ResponseCodes; + public class BluetoothPbapVcardManager { private static final String TAG = "BluetoothPbapVcardManager"; @@ -115,7 +111,7 @@ public class BluetoothPbapVcardManager { } public final String getOwnerPhoneNumberVcard(final boolean vcardType21) { - VCardComposer composer = new VCardComposer(mContext); + BluetoothPbapCallLogComposer composer = new BluetoothPbapCallLogComposer(mContext, false); String name = BluetoothPbapService.getLocalPhoneName(); String number = BluetoothPbapService.getLocalPhoneNum(); String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number, @@ -425,33 +421,54 @@ public class BluetoothPbapVcardManager { long timestamp = 0; if (V) timestamp = System.currentTimeMillis(); - VCardComposer composer = null; - try { - // Currently only support Generic Vcard 2.1 and 3.0 - int vcardType; - if (vcardType21) { - vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8; - } else { - vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8; - } - - composer = new VCardComposer(mContext, vcardType, true); - composer.addHandler(new HandlerForStringBuffer(op, ownerVCard)); - final Uri contentUri = (isContacts ? Contacts.CONTENT_URI : CallLog.Calls.CONTENT_URI); - if (!composer.init(contentUri, selection, null, null)) { - return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; - } + if (isContacts) { + VCardComposer composer = null; + try { + // Currently only support Generic Vcard 2.1 and 3.0 + int vcardType; + if (vcardType21) { + vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8; + } else { + vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8; + } - while (!composer.isAfterLast()) { - if (!composer.createOneEntry()) { - Log.e(TAG, "Failed to read a contact. Error reason: " - + composer.getErrorReason()); + composer = new VCardComposer(mContext, vcardType, true); + if (!composer.init(Contacts.CONTENT_URI, selection, null, null)) { return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; } + + while (!composer.isAfterLast()) { + if (!composer.createOneEntry()) { + Log.e(TAG, "Failed to read a contact. Error reason: " + + composer.getErrorReason()); + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + } + } finally { + if (composer != null) { + composer.terminate(); + } } - } finally { - if (composer != null) { - composer.terminate(); + } else { // CallLog + BluetoothPbapCallLogComposer composer = null; + try { + composer = new BluetoothPbapCallLogComposer(mContext, true); + composer.addHandler(new HandlerForStringBuffer(op, ownerVCard)); + if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null, null)) { + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + while (!composer.isAfterLast()) { + if (!composer.createOneEntry()) { + Log.e(TAG, "Failed to read a contact. Error reason: " + + composer.getErrorReason()); + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + } + } finally { + if (composer != null) { + composer.terminate(); + } } }