From d3f6a6c56dec7d47bae1121ed1030f67c26736fe Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Thu, 25 Jun 2015 16:53:56 -0700 Subject: Cache repeated Telecom requests from call log. This improves call log scrolling performance. + Split "Wrapper" into a utility and a cache. + Use cache for repeated calls related to call logs. + In the process of fixing plumbing and typer, moved some phone call detail classes into the more appropriate call log package. + Update tests. Bug: 20524705 Change-Id: Ib8ee21e417c19f98f6474a5793416e8f99103b55 --- src/com/android/dialer/CallDetailActivity.java | 11 +- src/com/android/dialer/PhoneCallDetailsHelper.java | 267 --------------------- src/com/android/dialer/PhoneCallDetailsViews.java | 73 ------ src/com/android/dialer/calllog/CallLogAdapter.java | 30 +-- .../dialer/calllog/CallLogAsyncTaskUtil.java | 8 +- .../dialer/calllog/CallLogListItemHelper.java | 16 +- .../dialer/calllog/CallLogListItemViewHolder.java | 30 ++- .../android/dialer/calllog/PhoneAccountUtils.java | 4 +- .../dialer/calllog/PhoneCallDetailsHelper.java | 267 +++++++++++++++++++++ .../dialer/calllog/PhoneCallDetailsViews.java | 73 ++++++ .../dialer/calllog/PhoneNumberDisplayUtil.java | 3 +- .../dialer/calllog/PhoneNumberUtilsWrapper.java | 131 ---------- .../dialer/calllog/TelecomCallLogCache.java | 124 ++++++++++ src/com/android/dialer/util/PhoneNumberUtil.java | 95 ++++++++ 14 files changed, 611 insertions(+), 521 deletions(-) delete mode 100644 src/com/android/dialer/PhoneCallDetailsHelper.java delete mode 100644 src/com/android/dialer/PhoneCallDetailsViews.java create mode 100644 src/com/android/dialer/calllog/PhoneCallDetailsHelper.java create mode 100644 src/com/android/dialer/calllog/PhoneCallDetailsViews.java delete mode 100644 src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java create mode 100644 src/com/android/dialer/calllog/TelecomCallLogCache.java create mode 100644 src/com/android/dialer/util/PhoneNumberUtil.java (limited to 'src/com/android') diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java index b851372c0..734e78f46 100644 --- a/src/com/android/dialer/CallDetailActivity.java +++ b/src/com/android/dialer/CallDetailActivity.java @@ -57,9 +57,9 @@ import com.android.dialer.calllog.ContactInfo; import com.android.dialer.calllog.ContactInfoHelper; import com.android.dialer.calllog.PhoneAccountUtils; import com.android.dialer.calllog.PhoneNumberDisplayUtil; -import com.android.dialer.calllog.PhoneNumberUtilsWrapper; -import com.android.dialer.util.IntentUtil; import com.android.dialer.util.DialerUtils; +import com.android.dialer.util.IntentUtil; +import com.android.dialer.util.PhoneNumberUtil; import com.android.dialer.util.TelecomUtil; import java.util.List; @@ -116,11 +116,10 @@ public class CallDetailActivity extends Activity // Cache the details about the phone number. final boolean canPlaceCallsTo = - PhoneNumberUtilsWrapper.canPlaceCallsTo(mNumber, numberPresentation); - final PhoneNumberUtilsWrapper phoneUtils = new PhoneNumberUtilsWrapper(mContext); + PhoneNumberUtil.canPlaceCallsTo(mNumber, numberPresentation); mIsVoicemailNumber = - phoneUtils.isVoicemailNumber(accountHandle, mNumber); - final boolean isSipNumber = PhoneNumberUtilsWrapper.isSipNumber(mNumber); + PhoneNumberUtil.isVoicemailNumber(mContext, accountHandle, mNumber); + final boolean isSipNumber = PhoneNumberUtil.isSipNumber(mNumber); final CharSequence callLocationOrType = getNumberTypeOrLocation(firstDetails); diff --git a/src/com/android/dialer/PhoneCallDetailsHelper.java b/src/com/android/dialer/PhoneCallDetailsHelper.java deleted file mode 100644 index 2dc0810e8..000000000 --- a/src/com/android/dialer/PhoneCallDetailsHelper.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2011 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.dialer; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.telecom.PhoneAccount; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.TextView; - -import com.android.contacts.common.testing.NeededForTesting; -import com.android.contacts.common.util.PhoneNumberHelper; -import com.android.dialer.calllog.ContactInfo; -import com.android.dialer.calllog.PhoneAccountUtils; -import com.android.dialer.calllog.PhoneNumberUtilsWrapper; -import com.android.dialer.util.DialerUtils; - -import com.google.common.collect.Lists; - -import java.util.ArrayList; - -/** - * Helper class to fill in the views in {@link PhoneCallDetailsViews}. - */ -public class PhoneCallDetailsHelper { - /** The maximum number of icons will be shown to represent the call types in a group. */ - private static final int MAX_CALL_TYPE_ICONS = 3; - - private final Context mContext; - private final Resources mResources; - /** The injected current time in milliseconds since the epoch. Used only by tests. */ - private Long mCurrentTimeMillisForTest; - // Helper classes. - private final PhoneNumberUtilsWrapper mPhoneNumberUtilsWrapper; - - /** - * List of items to be concatenated together for accessibility descriptions - */ - private ArrayList mDescriptionItems = Lists.newArrayList(); - - /** - * Creates a new instance of the helper. - *

- * Generally you should have a single instance of this helper in any context. - * - * @param resources used to look up strings - */ - public PhoneCallDetailsHelper(Context context, Resources resources, - PhoneNumberUtilsWrapper phoneUtils) { - mContext = context; - mResources = resources; - mPhoneNumberUtilsWrapper = phoneUtils; - } - - /** Fills the call details views with content. */ - public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details) { - // Display up to a given number of icons. - views.callTypeIcons.clear(); - int count = details.callTypes.length; - boolean isVoicemail = false; - for (int index = 0; index < count && index < MAX_CALL_TYPE_ICONS; ++index) { - views.callTypeIcons.add(details.callTypes[index]); - if (index == 0) { - isVoicemail = details.callTypes[index] == Calls.VOICEMAIL_TYPE; - } - } - - // Show the video icon if the call had video enabled. - views.callTypeIcons.setShowVideo( - (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO); - views.callTypeIcons.requestLayout(); - views.callTypeIcons.setVisibility(View.VISIBLE); - - // Show the total call count only if there are more than the maximum number of icons. - final Integer callCount; - if (count > MAX_CALL_TYPE_ICONS) { - callCount = count; - } else { - callCount = null; - } - - CharSequence callLocationAndDate = getCallLocationAndDate(details); - - // Set the call count, location and date. - setCallCountAndDate(views, callCount, callLocationAndDate); - - // Set the account label if it exists. - String accountLabel = PhoneAccountUtils.getAccountLabel(mContext, details.accountHandle); - - if (accountLabel != null) { - views.callAccountLabel.setVisibility(View.VISIBLE); - views.callAccountLabel.setText(accountLabel); - int color = PhoneAccountUtils.getAccountColor(mContext, details.accountHandle); - if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) { - int defaultColor = R.color.dialtacts_secondary_text_color; - views.callAccountLabel.setTextColor(mContext.getResources().getColor(defaultColor)); - } else { - views.callAccountLabel.setTextColor(color); - } - } else { - views.callAccountLabel.setVisibility(View.GONE); - } - - final CharSequence nameText; - final CharSequence displayNumber = details.displayNumber; - if (TextUtils.isEmpty(details.name)) { - nameText = displayNumber; - // We have a real phone number as "nameView" so make it always LTR - views.nameView.setTextDirection(View.TEXT_DIRECTION_LTR); - } else { - nameText = details.name; - } - - views.nameView.setText(nameText); - - if (isVoicemail && !TextUtils.isEmpty(details.transcription)) { - views.voicemailTranscriptionView.setText(details.transcription); - views.voicemailTranscriptionView.setVisibility(View.VISIBLE); - } else { - views.voicemailTranscriptionView.setText(null); - views.voicemailTranscriptionView.setVisibility(View.GONE); - } - - // Bold if not read - Typeface typeface = details.isRead ? Typeface.SANS_SERIF : Typeface.DEFAULT_BOLD; - views.nameView.setTypeface(typeface); - views.voicemailTranscriptionView.setTypeface(typeface); - views.callLocationAndDate.setTypeface(typeface); - } - - /** - * Builds a string containing the call location and date. - * - * @param details The call details. - * @return The call location and date string. - */ - private CharSequence getCallLocationAndDate(PhoneCallDetails details) { - mDescriptionItems.clear(); - - // Get type of call (ie mobile, home, etc) if known, or the caller's location. - CharSequence callTypeOrLocation = getCallTypeOrLocation(details); - - // Only add the call type or location if its not empty. It will be empty for unknown - // callers. - if (!TextUtils.isEmpty(callTypeOrLocation)) { - mDescriptionItems.add(callTypeOrLocation); - } - // The date of this call, relative to the current time. - mDescriptionItems.add(getCallDate(details)); - - // Create a comma separated list from the call type or location, and call date. - return DialerUtils.join(mResources, mDescriptionItems); - } - - /** - * For a call, if there is an associated contact for the caller, return the known call type - * (e.g. mobile, home, work). If there is no associated contact, attempt to use the caller's - * location if known. - * @param details Call details to use. - * @return Type of call (mobile/home) if known, or the location of the caller (if known). - */ - public CharSequence getCallTypeOrLocation(PhoneCallDetails details) { - CharSequence numberFormattedLabel = null; - // Only show a label if the number is shown and it is not a SIP address. - if (!TextUtils.isEmpty(details.number) - && !PhoneNumberHelper.isUriNumber(details.number.toString()) - && !mPhoneNumberUtilsWrapper.isVoicemailNumber(details.accountHandle, - details.number)) { - - if (TextUtils.isEmpty(details.name) && !TextUtils.isEmpty(details.geocode)) { - numberFormattedLabel = details.geocode; - } else if (!(details.numberType == Phone.TYPE_CUSTOM - && TextUtils.isEmpty(details.numberLabel))) { - // Get type label only if it will not be "Custom" because of an empty number label. - numberFormattedLabel = Phone.getTypeLabel( - mResources, details.numberType, details.numberLabel); - } - } - - if (!TextUtils.isEmpty(details.name) && TextUtils.isEmpty(numberFormattedLabel)) { - numberFormattedLabel = details.displayNumber; - } - return numberFormattedLabel; - } - - /** - * Get the call date/time of the call, relative to the current time. - * e.g. 3 minutes ago - * @param details Call details to use. - * @return String representing when the call occurred. - */ - public CharSequence getCallDate(PhoneCallDetails details) { - return DateUtils.getRelativeTimeSpanString(details.date, - getCurrentTimeMillis(), - DateUtils.MINUTE_IN_MILLIS, - DateUtils.FORMAT_ABBREV_RELATIVE); - } - - /** Sets the text of the header view for the details page of a phone call. */ - @NeededForTesting - public void setCallDetailsHeader(TextView nameView, PhoneCallDetails details) { - final CharSequence nameText; - if (!TextUtils.isEmpty(details.name)) { - nameText = details.name; - } else if (!TextUtils.isEmpty(details.displayNumber)) { - nameText = details.displayNumber; - } else { - nameText = mResources.getString(R.string.unknown); - } - - nameView.setText(nameText); - } - - @NeededForTesting - public void setCurrentTimeForTest(long currentTimeMillis) { - mCurrentTimeMillisForTest = currentTimeMillis; - } - - /** - * Returns the current time in milliseconds since the epoch. - *

- * It can be injected in tests using {@link #setCurrentTimeForTest(long)}. - */ - private long getCurrentTimeMillis() { - if (mCurrentTimeMillisForTest == null) { - return System.currentTimeMillis(); - } else { - return mCurrentTimeMillisForTest; - } - } - - /** Sets the call count and date. */ - private void setCallCountAndDate(PhoneCallDetailsViews views, Integer callCount, - CharSequence dateText) { - // Combine the count (if present) and the date. - final CharSequence text; - if (callCount != null) { - text = mResources.getString( - R.string.call_log_item_count_and_date, callCount.intValue(), dateText); - } else { - text = dateText; - } - - views.callLocationAndDate.setText(text); - } -} diff --git a/src/com/android/dialer/PhoneCallDetailsViews.java b/src/com/android/dialer/PhoneCallDetailsViews.java deleted file mode 100644 index 05026d6ee..000000000 --- a/src/com/android/dialer/PhoneCallDetailsViews.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2011 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.dialer; - -import android.content.Context; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.dialer.calllog.CallTypeIconsView; - -/** - * Encapsulates the views that are used to display the details of a phone call in the call log. - */ -public final class PhoneCallDetailsViews { - public final TextView nameView; - public final View callTypeView; - public final CallTypeIconsView callTypeIcons; - public final TextView callLocationAndDate; - public final TextView voicemailTranscriptionView; - public final TextView callAccountLabel; - - private PhoneCallDetailsViews(TextView nameView, View callTypeView, - CallTypeIconsView callTypeIcons, TextView callLocationAndDate, - TextView voicemailTranscriptionView, TextView callAccountLabel) { - this.nameView = nameView; - this.callTypeView = callTypeView; - this.callTypeIcons = callTypeIcons; - this.callLocationAndDate = callLocationAndDate; - this.voicemailTranscriptionView = voicemailTranscriptionView; - this.callAccountLabel = callAccountLabel; - } - - /** - * Create a new instance by extracting the elements from the given view. - *

- * The view should contain three text views with identifiers {@code R.id.name}, - * {@code R.id.date}, and {@code R.id.number}, and a linear layout with identifier - * {@code R.id.call_types}. - */ - public static PhoneCallDetailsViews fromView(View view) { - return new PhoneCallDetailsViews((TextView) view.findViewById(R.id.name), - view.findViewById(R.id.call_type), - (CallTypeIconsView) view.findViewById(R.id.call_type_icons), - (TextView) view.findViewById(R.id.call_location_and_date), - (TextView) view.findViewById(R.id.voicemail_transcription), - (TextView) view.findViewById(R.id.call_account_label)); - } - - public static PhoneCallDetailsViews createForTest(Context context) { - return new PhoneCallDetailsViews( - new TextView(context), - new View(context), - new CallTypeIconsView(context), - new TextView(context), - new TextView(context), - new TextView(context)); - } -} diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index 2ba257a3b..bb776d29e 100644 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -42,10 +42,10 @@ import android.view.accessibility.AccessibilityEvent; import com.android.contacts.common.util.PermissionsUtil; import com.android.dialer.PhoneCallDetails; -import com.android.dialer.PhoneCallDetailsHelper; import com.android.dialer.R; import com.android.dialer.contactinfo.ContactInfoCache; import com.android.dialer.contactinfo.ContactInfoCache.OnContactInfoChangedListener; +import com.android.dialer.util.PhoneNumberUtil; import com.android.dialer.voicemail.VoicemailPlaybackPresenter; import com.google.common.annotations.VisibleForTesting; @@ -123,10 +123,11 @@ public class CallLogAdapter extends GroupingListAdapter private boolean mShowPromoCard = false; /** Instance of helper class for managing views. */ - private final CallLogListItemHelper mCallLogViewsHelper; + private final CallLogListItemHelper mCallLogListItemHelper; + + /** Cache for repeated requests to TelecomManager. */ + protected final TelecomCallLogCache mTelecomCallLogCache; - /** Helper to access Telephony phone number utils class */ - protected final PhoneNumberUtilsWrapper mPhoneNumberUtilsWrapper; /** Helper to group call log entries. */ private final CallLogGroupBuilder mCallLogGroupBuilder; @@ -256,10 +257,11 @@ public class CallLogAdapter extends GroupingListAdapter Resources resources = mContext.getResources(); CallTypeHelper callTypeHelper = new CallTypeHelper(resources); - mPhoneNumberUtilsWrapper = new PhoneNumberUtilsWrapper(mContext); + mTelecomCallLogCache = new TelecomCallLogCache(mContext); PhoneCallDetailsHelper phoneCallDetailsHelper = - new PhoneCallDetailsHelper(mContext, resources, mPhoneNumberUtilsWrapper); - mCallLogViewsHelper = new CallLogListItemHelper(phoneCallDetailsHelper, resources); + new PhoneCallDetailsHelper(mContext, resources, mTelecomCallLogCache); + mCallLogListItemHelper = + new CallLogListItemHelper(phoneCallDetailsHelper, resources, mTelecomCallLogCache); mCallLogGroupBuilder = new CallLogGroupBuilder(this); mPrefs = PreferenceManager.getDefaultSharedPreferences(context); maybeShowVoicemailPromoCard(); @@ -329,6 +331,7 @@ public class CallLogAdapter extends GroupingListAdapter public void pauseCache() { mContactInfoCache.stop(); + mTelecomCallLogCache.reset(); } @Override @@ -359,8 +362,8 @@ public class CallLogAdapter extends GroupingListAdapter view, mContext, mExpandCollapseListener, - mPhoneNumberUtilsWrapper, - mCallLogViewsHelper, + mTelecomCallLogCache, + mCallLogListItemHelper, mVoicemailPlaybackPresenter); viewHolder.callLogEntryView.setTag(viewHolder); @@ -432,14 +435,13 @@ public class CallLogAdapter extends GroupingListAdapter final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO); final ContactInfo cachedContactInfo = mContactInfoHelper.getContactInfo(c); final boolean isVoicemailNumber = - mPhoneNumberUtilsWrapper.isVoicemailNumber(accountHandle, number); + mTelecomCallLogCache.isVoicemailNumber(accountHandle, number); // Note: Binding of the action buttons is done as required in configureActionViews when the // user expands the actions ViewStub. ContactInfo info = ContactInfo.EMPTY; - if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation) - && !isVoicemailNumber) { + if (PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemailNumber) { // Lookup contacts with this number info = mContactInfoCache.getValue(number, countryIso, cachedContactInfo); } @@ -499,7 +501,7 @@ public class CallLogAdapter extends GroupingListAdapter views.dayGroupHeader.setVisibility(View.GONE); } - mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details); + mCallLogListItemHelper.setPhoneCallDetails(views, details); if (mCurrentlyExpandedRowId == views.rowId) { // In case ViewHolders were added/removed, update the expanded position if the rowIds @@ -522,7 +524,7 @@ public class CallLogAdapter extends GroupingListAdapter views.setPhoto(info.photoId, info.photoUri, info.lookupUri, nameForDefaultImage, isVoicemailNumber, mContactInfoHelper.isBusiness(info.sourceType)); - mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details); + mCallLogListItemHelper.setPhoneCallDetails(views, details); // Listen for the first draw if (mViewTreeObserver == null) { diff --git a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java index 812e1a77a..1becc89af 100644 --- a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java +++ b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java @@ -32,6 +32,7 @@ import com.android.contacts.common.GeoUtil; import com.android.dialer.PhoneCallDetails; import com.android.dialer.util.AsyncTaskExecutor; import com.android.dialer.util.AsyncTaskExecutors; +import com.android.dialer.util.PhoneNumberUtil; import com.android.dialer.util.TelecomUtil; import com.google.common.annotations.VisibleForTesting; @@ -151,12 +152,9 @@ public class CallLogAsyncTaskUtil { // If this is not a regular number, there is no point in looking it up in the contacts. ContactInfoHelper contactInfoHelper = new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)); - PhoneNumberUtilsWrapper phoneNumberUtilsWrapper = - new PhoneNumberUtilsWrapper(context); - boolean isVoicemail = phoneNumberUtilsWrapper.isVoicemailNumber(accountHandle, number); + boolean isVoicemail = PhoneNumberUtil.isVoicemailNumber(context, accountHandle, number); boolean shouldLookupNumber = - PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation) - && !isVoicemail; + PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemail; ContactInfo info = shouldLookupNumber ? contactInfoHelper.lookupNumber(number, countryIso) : ContactInfo.EMPTY; diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java index d08810706..1c8e397e4 100644 --- a/src/com/android/dialer/calllog/CallLogListItemHelper.java +++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java @@ -24,7 +24,6 @@ import android.text.TextUtils; import android.util.Log; import com.android.dialer.PhoneCallDetails; -import com.android.dialer.PhoneCallDetailsHelper; import com.android.dialer.R; /** @@ -37,6 +36,7 @@ import com.android.dialer.R; private final PhoneCallDetailsHelper mPhoneCallDetailsHelper; /** Resources to look up strings. */ private final Resources mResources; + private final TelecomCallLogCache mTelecomCallLogCache; /** * Creates a new helper instance. @@ -45,9 +45,12 @@ import com.android.dialer.R; * @param phoneNumberHelper used to process phone number */ public CallLogListItemHelper( - PhoneCallDetailsHelper phoneCallDetailsHelper, Resources resources) { + PhoneCallDetailsHelper phoneCallDetailsHelper, + Resources resources, + TelecomCallLogCache telecomCallLogCache) { mPhoneCallDetailsHelper = phoneCallDetailsHelper; mResources = resources; + mTelecomCallLogCache = telecomCallLogCache; } /** @@ -58,14 +61,15 @@ import com.android.dialer.R; * @param details the details of a phone call needed to fill in the data */ public void setPhoneCallDetails( - Context context, CallLogListItemViewHolder views, PhoneCallDetails details) { + CallLogListItemViewHolder views, + PhoneCallDetails details) { mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details); // Set the accessibility text for the contact badge views.quickContactView.setContentDescription(getContactBadgeDescription(details)); // Set the primary action accessibility description - views.primaryActionView.setContentDescription(getCallDescription(context, details)); + views.primaryActionView.setContentDescription(getCallDescription(details)); // Cache name or number of caller. Used when setting the content descriptions of buttons // when the actions ViewStub is inflated. @@ -151,7 +155,7 @@ import com.android.dialer.R; * @param details Details of call. * @return Return call action description. */ - public CharSequence getCallDescription(Context context, PhoneCallDetails details) { + public CharSequence getCallDescription(PhoneCallDetails details) { int lastCallType = getLastCallType(details.callTypes); boolean isVoiceMail = lastCallType == Calls.VOICEMAIL_TYPE; @@ -183,7 +187,7 @@ import com.android.dialer.R; } int stringID = getCallDescriptionStringID(details.callTypes); - String accountLabel = PhoneAccountUtils.getAccountLabel(context, details.accountHandle); + String accountLabel = mTelecomCallLogCache.getAccountLabel(details.accountHandle); // Use chosen string resource to build up the message. CharSequence onAccountLabel = accountLabel == null diff --git a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java index f54720b31..361e1c7a3 100644 --- a/src/com/android/dialer/calllog/CallLogListItemViewHolder.java +++ b/src/com/android/dialer/calllog/CallLogListItemViewHolder.java @@ -39,11 +39,10 @@ import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; import com.android.contacts.common.testing.NeededForTesting; import com.android.contacts.common.util.UriUtils; -import com.android.dialer.PhoneCallDetailsHelper; -import com.android.dialer.PhoneCallDetailsViews; import com.android.dialer.R; import com.android.dialer.calllog.CallLogAsyncTaskUtil; import com.android.dialer.util.DialerUtils; +import com.android.dialer.util.PhoneNumberUtil; import com.android.dialer.voicemail.VoicemailPlaybackPresenter; import com.android.dialer.voicemail.VoicemailPlaybackLayout; @@ -139,7 +138,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder private static final int VOICEMAIL_TRANSCRIPTION_MAX_LINES = 10; private final Context mContext; - private final PhoneNumberUtilsWrapper mPhoneNumberUtilsWrapper; + private final TelecomCallLogCache mTelecomCallLogCache; private final CallLogListItemHelper mCallLogListItemHelper; private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; @@ -151,7 +150,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder private CallLogListItemViewHolder( Context context, View.OnClickListener expandCollapseListener, - PhoneNumberUtilsWrapper phoneNumberUtilsWrapper, + TelecomCallLogCache telecomCallLogCache, CallLogListItemHelper callLogListItemHelper, VoicemailPlaybackPresenter voicemailPlaybackPresenter, View rootView, @@ -165,7 +164,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder mContext = context; mExpandCollapseListener = expandCollapseListener; - mPhoneNumberUtilsWrapper = phoneNumberUtilsWrapper; + mTelecomCallLogCache = telecomCallLogCache; mCallLogListItemHelper = callLogListItemHelper; mVoicemailPlaybackPresenter = voicemailPlaybackPresenter; @@ -194,14 +193,14 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder View view, Context context, View.OnClickListener expandCollapseListener, - PhoneNumberUtilsWrapper phoneNumberUtilsWrapper, + TelecomCallLogCache telecomCallLogCache, CallLogListItemHelper callLogListItemHelper, VoicemailPlaybackPresenter voicemailPlaybackPresenter) { return new CallLogListItemViewHolder( context, expandCollapseListener, - phoneNumberUtilsWrapper, + telecomCallLogCache, callLogListItemHelper, voicemailPlaybackPresenter, view, @@ -263,11 +262,11 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } else { // Treat as normal list item; show call button, if possible. boolean canPlaceCallToNumber = - PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation); + PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation); if (canPlaceCallToNumber) { boolean isVoicemailNumber = - mPhoneNumberUtilsWrapper.isVoicemailNumber(accountHandle, number); + mTelecomCallLogCache.isVoicemailNumber(accountHandle, number); if (isVoicemailNumber) { // Call to generic voicemail number, in case there are multiple accounts. primaryActionButtonView.setTag( @@ -294,8 +293,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder * buttons. */ private void bindActionButtons() { - boolean canPlaceCallToNumber = - PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation); + boolean canPlaceCallToNumber = PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation); if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) { callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number)); @@ -309,7 +307,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } // If one of the calls had video capabilities, show the video call button. - if (CallUtil.isVideoEnabled(mContext) && canPlaceCallToNumber && + if (mTelecomCallLogCache.isVideoEnabled() && canPlaceCallToNumber && phoneCallDetailsViews.callTypeIcons.isVideoShown()) { videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number)); videoCallButtonView.setVisibility(View.VISIBLE); @@ -440,15 +438,15 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder @NeededForTesting public static CallLogListItemViewHolder createForTest(Context context) { Resources resources = context.getResources(); - PhoneNumberUtilsWrapper phoneNumberUtilsWrapper = new PhoneNumberUtilsWrapper(context); + TelecomCallLogCache telecomCallLogCache = new TelecomCallLogCache(context); PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper( - context, resources, phoneNumberUtilsWrapper); + context, resources, telecomCallLogCache); CallLogListItemViewHolder viewHolder = new CallLogListItemViewHolder( context, null /* expandCollapseListener */, - phoneNumberUtilsWrapper, - new CallLogListItemHelper(phoneCallDetailsHelper, resources), + telecomCallLogCache, + new CallLogListItemHelper(phoneCallDetailsHelper, resources, telecomCallLogCache), null /* voicemailPlaybackPresenter */, new View(context), new QuickContactBadge(context), diff --git a/src/com/android/dialer/calllog/PhoneAccountUtils.java b/src/com/android/dialer/calllog/PhoneAccountUtils.java index d63b940e7..7eaa523f4 100644 --- a/src/com/android/dialer/calllog/PhoneAccountUtils.java +++ b/src/com/android/dialer/calllog/PhoneAccountUtils.java @@ -27,7 +27,7 @@ import java.util.ArrayList; import java.util.List; /** - * Methods to help extract {@code PhoneAccount} information from database and Telecomm sources + * Methods to help extract {@code PhoneAccount} information from database and Telecomm sources. */ public class PhoneAccountUtils { /** @@ -87,7 +87,7 @@ public class PhoneAccountUtils { * Retrieve the account metadata, but if the account does not exist or the device has only a * single registered and enabled account, return null. */ - private static PhoneAccount getAccountOrNull(Context context, + static PhoneAccount getAccountOrNull(Context context, PhoneAccountHandle accountHandle) { TelecomManager telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); diff --git a/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java new file mode 100644 index 000000000..df5fe0606 --- /dev/null +++ b/src/com/android/dialer/calllog/PhoneCallDetailsHelper.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2011 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.dialer.calllog; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.telecom.PhoneAccount; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.view.View; +import android.widget.TextView; + +import com.android.contacts.common.testing.NeededForTesting; +import com.android.contacts.common.util.PhoneNumberHelper; +import com.android.dialer.PhoneCallDetails; +import com.android.dialer.R; +import com.android.dialer.util.DialerUtils; +import com.android.dialer.util.PhoneNumberUtil; + +import com.google.common.collect.Lists; + +import java.util.ArrayList; + +/** + * Helper class to fill in the views in {@link PhoneCallDetailsViews}. + */ +public class PhoneCallDetailsHelper { + /** The maximum number of icons will be shown to represent the call types in a group. */ + private static final int MAX_CALL_TYPE_ICONS = 3; + + private final Context mContext; + private final Resources mResources; + /** The injected current time in milliseconds since the epoch. Used only by tests. */ + private Long mCurrentTimeMillisForTest; + private final TelecomCallLogCache mTelecomCallLogCache; + + /** + * List of items to be concatenated together for accessibility descriptions + */ + private ArrayList mDescriptionItems = Lists.newArrayList(); + + /** + * Creates a new instance of the helper. + *

+ * Generally you should have a single instance of this helper in any context. + * + * @param resources used to look up strings + */ + public PhoneCallDetailsHelper( + Context context, + Resources resources, + TelecomCallLogCache telecomCallLogCache) { + mContext = context; + mResources = resources; + mTelecomCallLogCache = telecomCallLogCache; + } + + /** Fills the call details views with content. */ + public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details) { + // Display up to a given number of icons. + views.callTypeIcons.clear(); + int count = details.callTypes.length; + boolean isVoicemail = false; + for (int index = 0; index < count && index < MAX_CALL_TYPE_ICONS; ++index) { + views.callTypeIcons.add(details.callTypes[index]); + if (index == 0) { + isVoicemail = details.callTypes[index] == Calls.VOICEMAIL_TYPE; + } + } + + // Show the video icon if the call had video enabled. + views.callTypeIcons.setShowVideo( + (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO); + views.callTypeIcons.requestLayout(); + views.callTypeIcons.setVisibility(View.VISIBLE); + + // Show the total call count only if there are more than the maximum number of icons. + final Integer callCount; + if (count > MAX_CALL_TYPE_ICONS) { + callCount = count; + } else { + callCount = null; + } + + CharSequence callLocationAndDate = getCallLocationAndDate(details); + + // Set the call count, location and date. + setCallCountAndDate(views, callCount, callLocationAndDate); + + // Set the account label if it exists. + String accountLabel = mTelecomCallLogCache.getAccountLabel(details.accountHandle); + + if (accountLabel != null) { + views.callAccountLabel.setVisibility(View.VISIBLE); + views.callAccountLabel.setText(accountLabel); + int color = PhoneAccountUtils.getAccountColor(mContext, details.accountHandle); + if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) { + int defaultColor = R.color.dialtacts_secondary_text_color; + views.callAccountLabel.setTextColor(mContext.getResources().getColor(defaultColor)); + } else { + views.callAccountLabel.setTextColor(color); + } + } else { + views.callAccountLabel.setVisibility(View.GONE); + } + + final CharSequence nameText; + final CharSequence displayNumber = details.displayNumber; + if (TextUtils.isEmpty(details.name)) { + nameText = displayNumber; + // We have a real phone number as "nameView" so make it always LTR + views.nameView.setTextDirection(View.TEXT_DIRECTION_LTR); + } else { + nameText = details.name; + } + + views.nameView.setText(nameText); + + if (isVoicemail && !TextUtils.isEmpty(details.transcription)) { + views.voicemailTranscriptionView.setText(details.transcription); + views.voicemailTranscriptionView.setVisibility(View.VISIBLE); + } else { + views.voicemailTranscriptionView.setText(null); + views.voicemailTranscriptionView.setVisibility(View.GONE); + } + + // Bold if not read + Typeface typeface = details.isRead ? Typeface.SANS_SERIF : Typeface.DEFAULT_BOLD; + views.nameView.setTypeface(typeface); + views.voicemailTranscriptionView.setTypeface(typeface); + views.callLocationAndDate.setTypeface(typeface); + } + + /** + * Builds a string containing the call location and date. + * + * @param details The call details. + * @return The call location and date string. + */ + private CharSequence getCallLocationAndDate(PhoneCallDetails details) { + mDescriptionItems.clear(); + + // Get type of call (ie mobile, home, etc) if known, or the caller's location. + CharSequence callTypeOrLocation = getCallTypeOrLocation(details); + + // Only add the call type or location if its not empty. It will be empty for unknown + // callers. + if (!TextUtils.isEmpty(callTypeOrLocation)) { + mDescriptionItems.add(callTypeOrLocation); + } + // The date of this call, relative to the current time. + mDescriptionItems.add(getCallDate(details)); + + // Create a comma separated list from the call type or location, and call date. + return DialerUtils.join(mResources, mDescriptionItems); + } + + /** + * For a call, if there is an associated contact for the caller, return the known call type + * (e.g. mobile, home, work). If there is no associated contact, attempt to use the caller's + * location if known. + * @param details Call details to use. + * @return Type of call (mobile/home) if known, or the location of the caller (if known). + */ + public CharSequence getCallTypeOrLocation(PhoneCallDetails details) { + CharSequence numberFormattedLabel = null; + // Only show a label if the number is shown and it is not a SIP address. + if (!TextUtils.isEmpty(details.number) + && !PhoneNumberHelper.isUriNumber(details.number.toString()) + && !mTelecomCallLogCache.isVoicemailNumber(details.accountHandle, details.number)) { + + if (TextUtils.isEmpty(details.name) && !TextUtils.isEmpty(details.geocode)) { + numberFormattedLabel = details.geocode; + } else if (!(details.numberType == Phone.TYPE_CUSTOM + && TextUtils.isEmpty(details.numberLabel))) { + // Get type label only if it will not be "Custom" because of an empty number label. + numberFormattedLabel = Phone.getTypeLabel( + mResources, details.numberType, details.numberLabel); + } + } + + if (!TextUtils.isEmpty(details.name) && TextUtils.isEmpty(numberFormattedLabel)) { + numberFormattedLabel = details.displayNumber; + } + return numberFormattedLabel; + } + + /** + * Get the call date/time of the call, relative to the current time. + * e.g. 3 minutes ago + * @param details Call details to use. + * @return String representing when the call occurred. + */ + public CharSequence getCallDate(PhoneCallDetails details) { + return DateUtils.getRelativeTimeSpanString(details.date, + getCurrentTimeMillis(), + DateUtils.MINUTE_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE); + } + + /** Sets the text of the header view for the details page of a phone call. */ + @NeededForTesting + public void setCallDetailsHeader(TextView nameView, PhoneCallDetails details) { + final CharSequence nameText; + if (!TextUtils.isEmpty(details.name)) { + nameText = details.name; + } else if (!TextUtils.isEmpty(details.displayNumber)) { + nameText = details.displayNumber; + } else { + nameText = mResources.getString(R.string.unknown); + } + + nameView.setText(nameText); + } + + @NeededForTesting + public void setCurrentTimeForTest(long currentTimeMillis) { + mCurrentTimeMillisForTest = currentTimeMillis; + } + + /** + * Returns the current time in milliseconds since the epoch. + *

+ * It can be injected in tests using {@link #setCurrentTimeForTest(long)}. + */ + private long getCurrentTimeMillis() { + if (mCurrentTimeMillisForTest == null) { + return System.currentTimeMillis(); + } else { + return mCurrentTimeMillisForTest; + } + } + + /** Sets the call count and date. */ + private void setCallCountAndDate(PhoneCallDetailsViews views, Integer callCount, + CharSequence dateText) { + // Combine the count (if present) and the date. + final CharSequence text; + if (callCount != null) { + text = mResources.getString( + R.string.call_log_item_count_and_date, callCount.intValue(), dateText); + } else { + text = dateText; + } + + views.callLocationAndDate.setText(text); + } +} diff --git a/src/com/android/dialer/calllog/PhoneCallDetailsViews.java b/src/com/android/dialer/calllog/PhoneCallDetailsViews.java new file mode 100644 index 000000000..94f4411b0 --- /dev/null +++ b/src/com/android/dialer/calllog/PhoneCallDetailsViews.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011 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.dialer.calllog; + +import android.content.Context; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.dialer.R; + +/** + * Encapsulates the views that are used to display the details of a phone call in the call log. + */ +public final class PhoneCallDetailsViews { + public final TextView nameView; + public final View callTypeView; + public final CallTypeIconsView callTypeIcons; + public final TextView callLocationAndDate; + public final TextView voicemailTranscriptionView; + public final TextView callAccountLabel; + + private PhoneCallDetailsViews(TextView nameView, View callTypeView, + CallTypeIconsView callTypeIcons, TextView callLocationAndDate, + TextView voicemailTranscriptionView, TextView callAccountLabel) { + this.nameView = nameView; + this.callTypeView = callTypeView; + this.callTypeIcons = callTypeIcons; + this.callLocationAndDate = callLocationAndDate; + this.voicemailTranscriptionView = voicemailTranscriptionView; + this.callAccountLabel = callAccountLabel; + } + + /** + * Create a new instance by extracting the elements from the given view. + *

+ * The view should contain three text views with identifiers {@code R.id.name}, + * {@code R.id.date}, and {@code R.id.number}, and a linear layout with identifier + * {@code R.id.call_types}. + */ + public static PhoneCallDetailsViews fromView(View view) { + return new PhoneCallDetailsViews((TextView) view.findViewById(R.id.name), + view.findViewById(R.id.call_type), + (CallTypeIconsView) view.findViewById(R.id.call_type_icons), + (TextView) view.findViewById(R.id.call_location_and_date), + (TextView) view.findViewById(R.id.voicemail_transcription), + (TextView) view.findViewById(R.id.call_account_label)); + } + + public static PhoneCallDetailsViews createForTest(Context context) { + return new PhoneCallDetailsViews( + new TextView(context), + new View(context), + new CallTypeIconsView(context), + new TextView(context), + new TextView(context), + new TextView(context)); + } +} diff --git a/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java b/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java index f80c2bc6a..5030efd48 100644 --- a/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java +++ b/src/com/android/dialer/calllog/PhoneNumberDisplayUtil.java @@ -23,6 +23,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.dialer.R; +import com.android.dialer.util.PhoneNumberUtil; /** * Helper for formatting and managing the display of phone numbers. @@ -49,7 +50,7 @@ public class PhoneNumberDisplayUtil { if (isVoicemail) { return context.getResources().getString(R.string.voicemail); } - if (PhoneNumberUtilsWrapper.isLegacyUnknownNumbers(number)) { + if (PhoneNumberUtil.isLegacyUnknownNumbers(number)) { return context.getResources().getString(R.string.unknown); } return ""; diff --git a/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java b/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java deleted file mode 100644 index 6fa81435f..000000000 --- a/src/com/android/dialer/calllog/PhoneNumberUtilsWrapper.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2013 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.dialer.calllog; - -import android.content.Context; -import android.provider.CallLog; -import android.telecom.PhoneAccountHandle; -import android.telecom.TelecomManager; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; - -import com.android.contacts.common.util.PhoneNumberHelper; -import com.google.common.collect.Sets; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * - */ -public class PhoneNumberUtilsWrapper { - private static final Set LEGACY_UNKNOWN_NUMBERS = Sets.newHashSet("-1", "-2", "-3"); - private static final long MAX_VOICEMAIL_CACHE_AGE_IN_MS = 60 * 1000; // 60 seconds - private final Context mContext; - - // Keeps a cache of recently-made voicemail queries. The entire point of this cache is to - // reduce the number of cross-process requests to TelecomManager. - // Maps from a phone-account/number pair to a boolean because multiple numbers could return true - // for the voicemail number if those numbers are not pre-normalized. - // - // TODO: Dialer should be fixed so as not to check isVoicemail() so often but at the time of - // this writing, that was a much larger undertaking than creating this cache. - private final Map, Boolean> mVoicemailQueryCache = - new HashMap<>(); - private long mVoicemailCacheTimestamp = 0; - - public PhoneNumberUtilsWrapper(Context context) { - mContext = context; - } - - /** Returns true if it is possible to place a call to the given number. */ - public static boolean canPlaceCallsTo(CharSequence number, int presentation) { - return presentation == CallLog.Calls.PRESENTATION_ALLOWED - && !TextUtils.isEmpty(number) && !isLegacyUnknownNumbers(number); - } - - /** - * Returns true if the given number is the number of the configured voicemail. To be able to - * mock-out this, it is not a static method. - */ - public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) { - if (TextUtils.isEmpty(number)) { - return false; - } - - long currentTime = System.currentTimeMillis(); - // check the age of the voicemail cache first. - if (currentTime - mVoicemailCacheTimestamp > MAX_VOICEMAIL_CACHE_AGE_IN_MS) { - mVoicemailQueryCache.clear(); - - // We set the timestamp of the voicemail cache to the point where the cache is recreated - // instead of when an item is added. - // 1) This is easier to write - // 2) Ensures that the oldest entry is never older than MAX_VOICEMAIL_CACHE_AGE - mVoicemailCacheTimestamp = currentTime; - } - - Pair key = new Pair<>(accountHandle, number); - if (mVoicemailQueryCache.containsKey(key)) { - return mVoicemailQueryCache.get(key); - } else { - final TelecomManager telecomManager = - (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); - Boolean isVoicemail = - telecomManager.isVoiceMailNumber(accountHandle, number.toString()); - mVoicemailQueryCache.put(key, isVoicemail); - return isVoicemail; - } - } - - /** - * Returns true if the given number is a SIP address. To be able to mock-out this, it is not a - * static method. - */ - public static boolean isSipNumber(CharSequence number) { - return number != null && PhoneNumberHelper.isUriNumber(number.toString()); - } - - public boolean isUnknownNumberThatCanBeLookedUp(PhoneAccountHandle accountHandle, - CharSequence number, int presentation) { - if (presentation == CallLog.Calls.PRESENTATION_UNKNOWN) { - return false; - } - if (presentation == CallLog.Calls.PRESENTATION_RESTRICTED) { - return false; - } - if (presentation == CallLog.Calls.PRESENTATION_PAYPHONE) { - return false; - } - if (TextUtils.isEmpty(number)) { - return false; - } - if (isVoicemailNumber(accountHandle, number)) { - return false; - } - if (isLegacyUnknownNumbers(number)) { - return false; - } - return true; - } - - public static boolean isLegacyUnknownNumbers(CharSequence number) { - return number != null && LEGACY_UNKNOWN_NUMBERS.contains(number.toString()); - } -} diff --git a/src/com/android/dialer/calllog/TelecomCallLogCache.java b/src/com/android/dialer/calllog/TelecomCallLogCache.java new file mode 100644 index 000000000..ec1d24191 --- /dev/null +++ b/src/com/android/dialer/calllog/TelecomCallLogCache.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013 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.dialer.calllog; + +import android.content.Context; +import android.provider.CallLog; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.android.contacts.common.CallUtil; +import com.android.contacts.common.util.PhoneNumberHelper; +import com.android.dialer.util.PhoneNumberUtil; +import com.google.common.collect.Sets; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Keeps a cache of recently made queries to the Telecom process. The aim of this cache is to + * reduce the number of cross-process requests to TelecomManager, which can negatively affect + * performance. + * + * This is designed with the specific use case of the {@link CallLogAdapter} in mind. + */ +public class TelecomCallLogCache { + private final Context mContext; + + // Maps from a phone-account/number pair to a boolean because multiple numbers could return true + // for the voicemail number if those numbers are not pre-normalized. + // TODO: Dialer should be fixed so as not to check isVoicemail() so often but at the time of + // this writing, that was a much larger undertaking than creating this cache. + private final Map, Boolean> mVoicemailQueryCache = + new HashMap<>(); + private final Map mPhoneAccountLabelCache = new HashMap<>(); + private final Map mPhoneAccountColorCache = new HashMap<>(); + + private boolean mHasCheckedForVideoEnabled; + private boolean mIsVideoEnabled; + + public TelecomCallLogCache(Context context) { + mContext = context; + } + + public void reset() { + mVoicemailQueryCache.clear(); + mPhoneAccountLabelCache.clear(); + mPhoneAccountColorCache.clear(); + + mHasCheckedForVideoEnabled = false; + mIsVideoEnabled = false; + } + + /** + * Returns true if the given number is the number of the configured voicemail. To be able to + * mock-out this, it is not a static method. + */ + public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) { + if (TextUtils.isEmpty(number)) { + return false; + } + + Pair key = new Pair<>(accountHandle, number); + if (mVoicemailQueryCache.containsKey(key)) { + return mVoicemailQueryCache.get(key); + } else { + Boolean isVoicemail = + PhoneNumberUtil.isVoicemailNumber(mContext, accountHandle, number.toString()); + mVoicemailQueryCache.put(key, isVoicemail); + return isVoicemail; + } + } + + /** + * Extract account label from PhoneAccount object. + */ + public String getAccountLabel(PhoneAccountHandle accountHandle) { + if (mPhoneAccountLabelCache.containsKey(accountHandle)) { + return mPhoneAccountLabelCache.get(accountHandle); + } else { + String label = PhoneAccountUtils.getAccountLabel(mContext, accountHandle); + mPhoneAccountLabelCache.put(accountHandle, label); + return label; + } + } + + /** + * Extract account color from PhoneAccount object. + */ + public int getAccountColor(PhoneAccountHandle accountHandle) { + if (mPhoneAccountColorCache.containsKey(accountHandle)) { + return mPhoneAccountColorCache.get(accountHandle); + } else { + Integer color = PhoneAccountUtils.getAccountColor(mContext, accountHandle); + mPhoneAccountColorCache.put(accountHandle, color); + return color; + } + } + + public boolean isVideoEnabled() { + if (!mHasCheckedForVideoEnabled) { + mIsVideoEnabled = CallUtil.isVideoEnabled(mContext); + } + return mIsVideoEnabled; + } +} diff --git a/src/com/android/dialer/util/PhoneNumberUtil.java b/src/com/android/dialer/util/PhoneNumberUtil.java new file mode 100644 index 000000000..84f58aa85 --- /dev/null +++ b/src/com/android/dialer/util/PhoneNumberUtil.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2013 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.dialer.util; + +import android.content.Context; +import android.provider.CallLog; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.android.contacts.common.util.PhoneNumberHelper; +import com.google.common.collect.Sets; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class PhoneNumberUtil { + private static final Set LEGACY_UNKNOWN_NUMBERS = Sets.newHashSet("-1", "-2", "-3"); + + /** Returns true if it is possible to place a call to the given number. */ + public static boolean canPlaceCallsTo(CharSequence number, int presentation) { + return presentation == CallLog.Calls.PRESENTATION_ALLOWED + && !TextUtils.isEmpty(number) && !isLegacyUnknownNumbers(number); + } + + /** + * Returns true if the given number is the number of the configured voicemail. To be able to + * mock-out this, it is not a static method. + */ + public static boolean isVoicemailNumber( + Context context, PhoneAccountHandle accountHandle, CharSequence number) { + if (TextUtils.isEmpty(number)) { + return false; + } + + final TelecomManager telecomManager = + (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + return telecomManager.isVoiceMailNumber(accountHandle, number.toString()); + } + + /** + * Returns true if the given number is a SIP address. To be able to mock-out this, it is not a + * static method. + */ + public static boolean isSipNumber(CharSequence number) { + return number != null && PhoneNumberHelper.isUriNumber(number.toString()); + } + + public static boolean isUnknownNumberThatCanBeLookedUp( + Context context, + PhoneAccountHandle accountHandle, + CharSequence number, + int presentation) { + if (presentation == CallLog.Calls.PRESENTATION_UNKNOWN) { + return false; + } + if (presentation == CallLog.Calls.PRESENTATION_RESTRICTED) { + return false; + } + if (presentation == CallLog.Calls.PRESENTATION_PAYPHONE) { + return false; + } + if (TextUtils.isEmpty(number)) { + return false; + } + if (isVoicemailNumber(context, accountHandle, number)) { + return false; + } + if (isLegacyUnknownNumbers(number)) { + return false; + } + return true; + } + + public static boolean isLegacyUnknownNumbers(CharSequence number) { + return number != null && LEGACY_UNKNOWN_NUMBERS.contains(number.toString()); + } +} -- cgit v1.2.3