From 146a4cdf57f0d4d0cd85e808f1df2bdea639b24c Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Mon, 5 May 2014 16:51:42 -0700 Subject: Adding support for expandable call log entries. Bug: 13962594 Change-Id: I19a4a65dce922619df0a709293ca291c345c8be7 --- res/layout/call_log_fragment.xml | 3 +- res/layout/call_log_list_item.xml | 71 ++--- res/layout/call_log_list_item_actions.xml | 74 +++++ res/values/colors.xml | 12 +- res/values/dimens.xml | 16 +- res/values/strings.xml | 110 ++++--- res/values/styles.xml | 4 +- src/com/android/dialer/PhoneCallDetailsHelper.java | 76 +++-- src/com/android/dialer/PhoneCallDetailsViews.java | 15 +- src/com/android/dialer/calllog/CallLogAdapter.java | 317 ++++++++++++++++++--- .../android/dialer/calllog/CallLogFragment.java | 2 +- .../dialer/calllog/CallLogListItemHelper.java | 113 ++------ .../dialer/calllog/CallLogListItemViews.java | 64 ++++- src/com/android/dialer/list/ListsFragment.java | 2 +- src/com/android/dialerbind/ObjectFactory.java | 8 +- .../android/dialer/PhoneCallDetailsHelperTest.java | 16 +- .../android/dialer/calllog/CallLogAdapterTest.java | 2 +- .../dialer/calllog/CallLogFragmentTest.java | 19 +- .../dialer/calllog/CallLogListItemHelperTest.java | 112 ++------ 19 files changed, 641 insertions(+), 395 deletions(-) create mode 100644 res/layout/call_log_list_item_actions.xml diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml index aa8a185a8..23c7b143c 100644 --- a/res/layout/call_log_fragment.xml +++ b/res/layout/call_log_fragment.xml @@ -66,8 +66,7 @@ android:layout_height="match_parent" android:fadingEdge="none" android:scrollbarStyle="outsideOverlay" - android:divider="@color/favorite_contacts_separator_color" - android:dividerHeight="1dp" + android:divider="@null" /> - - - + android:layout_marginEnd="@dimen/call_log_icon_margin" + android:textColor="?attr/call_log_secondary_text_color" + android:textSize="@dimen/call_log_secondary_text_size" + android:singleLine="true" + android:ellipsize="marquee" + /> - - - - - - - + + + + + + + + + + + diff --git a/res/values/colors.xml b/res/values/colors.xml index 5a6227332..ffc6115bb 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -55,7 +55,7 @@ #ffffff - #ffffff + #fbfbfb @@ -107,4 +107,14 @@ #FFFFFF + + + #1dc7db + + + #ff4f4f + + + #b3b3b3 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 5095cddbe..d81c0f612 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -33,7 +33,7 @@ 8dip 24dip 56dip - 64dip + 40dip 24dip 2dip 37dp - 13sp + + 8dp + + 48dp + + 16sp + + 14sp + + 48dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 2575a0b1a..4815eb8e9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -472,59 +472,6 @@ --> %1$s calls. - - - Return missed call from %1$s %2$s %3$s - - - - Return answered call from %1$s %2$s %3$s - - - Missed call from %1$s %2$s %3$s - - - - Answered call from %1$s %2$s %3$s - - - Call %1$s %2$s. Last called %3$s. - - - Call %1$s %2$s. Called %3$s. - - Start voice search - - - Call %s @@ -774,4 +717,57 @@ sans-serif + + + CALL BACK + + + DELETE + + + LISTEN + + + Missed call from %1$s, %2$s, %3$s. + + + Answered call from %1$s, %2$s, %3$s. + + + Call to %1$s, %2$s, %3$s. + + + Call back %1$s + + + Listen to voicemail from %1$s + + + Delete call log entry for %1$s + + + Call log entry deleted. diff --git a/res/values/styles.xml b/res/values/styles.xml index 738a0b690..db56ab286 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -100,9 +100,9 @@ @style/DialtactsActionBarStyle @style/DialtactsActionBarOverflow - #333333 + #404040 #FFFFFF - #333333 + #9a9a9a #FFFFFF #FFFFFF diff --git a/src/com/android/dialer/PhoneCallDetailsHelper.java b/src/com/android/dialer/PhoneCallDetailsHelper.java index 4424fcb0b..edd083117 100644 --- a/src/com/android/dialer/PhoneCallDetailsHelper.java +++ b/src/com/android/dialer/PhoneCallDetailsHelper.java @@ -18,9 +18,7 @@ package com.android.dialer; import android.content.res.Resources; import android.graphics.Typeface; -import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.telephony.PhoneNumberUtils; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; @@ -36,6 +34,10 @@ import com.android.dialer.calllog.CallTypeHelper; import com.android.dialer.calllog.ContactInfo; import com.android.dialer.calllog.PhoneNumberDisplayHelper; import com.android.dialer.calllog.PhoneNumberUtilsWrapper; +import com.google.android.collect.Lists; + +import java.util.ArrayList; +import java.util.List; /** * Helper class to fill in the views in {@link PhoneCallDetailsViews}. @@ -52,6 +54,11 @@ public class PhoneCallDetailsHelper { private final PhoneNumberDisplayHelper mPhoneNumberHelper; 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. *

@@ -90,42 +97,54 @@ public class PhoneCallDetailsHelper { Integer highlightColor = isHighlighted ? mCallTypeHelper.getHighlightedColor(details.callTypes[0]) : null; - // The date of this call, relative to the current time. - CharSequence dateText = getCallDate(details); - - // Set the call count and date. - setCallCountAndDate(views, callCount, dateText, highlightColor); + CharSequence callLocationAndDate = getCallLocationAndDate(details); - // Get type of call (ie mobile, home, etc) if known, or the caller's - CharSequence numberFormattedLabel = getCallTypeOrLocation(details); + // Set the call count, location and date. + setCallCountAndDate(views, callCount, callLocationAndDate, highlightColor); final CharSequence nameText; - final CharSequence numberText; - final CharSequence labelText; final CharSequence displayNumber = mPhoneNumberHelper.getDisplayNumber(details.number, details.numberPresentation, details.formattedNumber); if (TextUtils.isEmpty(details.name)) { nameText = displayNumber; - if (TextUtils.isEmpty(details.geocode) - || mPhoneNumberUtilsWrapper.isVoicemailNumber(details.number)) { - numberText = mResources.getString(R.string.call_log_empty_geocode); - } else { - numberText = details.geocode; - } - labelText = numberText; // We have a real phone number as "nameView" so make it always LTR views.nameView.setTextDirection(View.TEXT_DIRECTION_LTR); } else { nameText = details.name; - numberText = displayNumber; - labelText = TextUtils.isEmpty(numberFormattedLabel) ? numberText : - numberFormattedLabel; } views.nameView.setText(nameText); - views.labelView.setText(labelText); - views.labelView.setVisibility(TextUtils.isEmpty(labelText) ? View.GONE : View.VISIBLE); + + // TODO: At the current time the voicemail transcription is not supported. This view + // is kept for future expansion when we may wish to show a transcription of voicemail. + views.voicemailTranscriptionView.setText(""); + views.voicemailTranscriptionView.setVisibility(View.GONE); + } + + /** + * 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. + // TextUtils.join ensures a locale appropriate list separator is used. + return TextUtils.join((List)mDescriptionItems); } /** @@ -139,7 +158,9 @@ public class PhoneCallDetailsHelper { 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())) { + && !PhoneNumberHelper.isUriNumber(details.number.toString()) + && !mPhoneNumberUtilsWrapper.isVoicemailNumber(details.number)) { + if (details.numberLabel == ContactInfo.GEOCODE_AS_LABEL) { numberFormattedLabel = details.geocode; } else { @@ -147,6 +168,11 @@ public class PhoneCallDetailsHelper { details.numberLabel); } } + + if (!TextUtils.isEmpty(details.name) && TextUtils.isEmpty(numberFormattedLabel)) { + numberFormattedLabel = mPhoneNumberHelper.getDisplayNumber(details.number, + details.numberPresentation, details.formattedNumber); + } return numberFormattedLabel; } @@ -216,7 +242,7 @@ public class PhoneCallDetailsHelper { formattedText = text; } - views.callTypeAndDate.setText(formattedText); + views.callLocationAndDate.setText(formattedText); } /** Creates a SpannableString for the given text which is bold and in the given color. */ diff --git a/src/com/android/dialer/PhoneCallDetailsViews.java b/src/com/android/dialer/PhoneCallDetailsViews.java index 4e482109b..30023ea11 100644 --- a/src/com/android/dialer/PhoneCallDetailsViews.java +++ b/src/com/android/dialer/PhoneCallDetailsViews.java @@ -29,16 +29,17 @@ public final class PhoneCallDetailsViews { public final TextView nameView; public final View callTypeView; public final CallTypeIconsView callTypeIcons; - public final TextView callTypeAndDate; - public final TextView labelView; + public final TextView callLocationAndDate; + public final TextView voicemailTranscriptionView; private PhoneCallDetailsViews(TextView nameView, View callTypeView, - CallTypeIconsView callTypeIcons, TextView callTypeAndDate, TextView labelView) { + CallTypeIconsView callTypeIcons, TextView callLocationAndDate, + TextView voicemailTranscriptionView) { this.nameView = nameView; this.callTypeView = callTypeView; this.callTypeIcons = callTypeIcons; - this.callTypeAndDate = callTypeAndDate; - this.labelView = labelView; + this.callLocationAndDate = callLocationAndDate; + this.voicemailTranscriptionView = voicemailTranscriptionView; } /** @@ -52,8 +53,8 @@ public final class PhoneCallDetailsViews { 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_count_and_date), - (TextView) view.findViewById(R.id.label)); + (TextView) view.findViewById(R.id.call_location_and_date), + (TextView) view.findViewById(R.id.voicemail_transcription)); } public static PhoneCallDetailsViews createForTest(Context context) { diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index 266be34ba..a8cd72a39 100644 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; +import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.provider.CallLog.Calls; @@ -35,20 +36,26 @@ import android.view.ViewStub; import android.view.ViewTreeObserver; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import com.android.common.widget.GroupingListAdapter; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; import com.android.contacts.common.util.UriUtils; +import com.android.dialer.CallDetailActivity; import com.android.dialer.PhoneCallDetails; import com.android.dialer.PhoneCallDetailsHelper; import com.android.dialer.R; +import com.android.dialer.util.AsyncTaskExecutor; +import com.android.dialer.util.AsyncTaskExecutors; import com.android.dialer.util.ExpirableCache; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; +import java.util.HashMap; import java.util.LinkedList; +import java.util.Map; /** * Adapter class to fill in data for the Call Log. @@ -56,6 +63,12 @@ import java.util.LinkedList; public class CallLogAdapter extends GroupingListAdapter implements ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator { + + /** The enumeration of {@link android.os.AsyncTask} objects used in this class. */ + public enum Tasks { + REMOVE_CALL_LOG_ENTRIES, + } + /** Interface used to initiate a refresh of the content. */ public interface CallFetcher { public void fetchCalls(); @@ -103,6 +116,9 @@ public class CallLogAdapter extends GroupingListAdapter private final CallFetcher mCallFetcher; private ViewTreeObserver mViewTreeObserver = null; + /** Aynchronous task executor, lazy instantiated as needed. */ + private AsyncTaskExecutor mAsyncTaskExecutor; + /** * A cache of the contact details for the phone numbers in the call log. *

@@ -113,6 +129,9 @@ public class CallLogAdapter extends GroupingListAdapter */ private ExpirableCache mContactInfoCache; + /** Hashmap, keyed by call Id, used to track which call log entries have been expanded or not */ + private HashMap mIsExpanded = new HashMap(); + /** * A request for contact details for the given number. */ @@ -186,12 +205,6 @@ public class CallLogAdapter extends GroupingListAdapter /** Can be set to true by tests to disable processing of requests. */ private volatile boolean mRequestProcessingDisabled = false; - /** - * Whether to show the secondary action button used to play voicemail or show call details. - * True if created from a CallLogFragment. - * False if created from the PhoneFavoriteFragment. */ - private boolean mShowSecondaryActionButton = true; - private boolean mIsCallLog = true; private int mNumMissedCalls = 0; private int mNumMissedCallsShown = 0; @@ -211,6 +224,35 @@ public class CallLogAdapter extends GroupingListAdapter } }; + /** + * Click listener for the delete from call log button. Removes the current call log + * entry and its associated calls from the call log. + */ + private final View.OnClickListener mDeleteListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + // Retrieve the views from the call log view. + final CallLogListItemViews views = + (CallLogListItemViews) + ((View)v.getParent().getParent().getParent().getParent()).getTag(); + + deleteCalls(views.callIds); + notifyDataSetChanged(); + } + }; + + /** + * The onClickListener used to expand or collapse the action buttons section for a call log + * entry. + */ + private final View.OnClickListener mExpandCollapseListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + expandOrCollapseActions((View) v.getParent().getParent()); + notifyDataSetChanged(); + } + }; + private void startActivityForAction(View view) { final IntentProvider intentProvider = (IntentProvider) view.getTag(); if (intentProvider != null) { @@ -251,14 +293,13 @@ public class CallLogAdapter extends GroupingListAdapter }; public CallLogAdapter(Context context, CallFetcher callFetcher, - ContactInfoHelper contactInfoHelper, boolean showSecondaryActionButton, + ContactInfoHelper contactInfoHelper, boolean isCallLog) { super(context); mContext = context; mCallFetcher = callFetcher; mContactInfoHelper = contactInfoHelper; - mShowSecondaryActionButton = showSecondaryActionButton; mIsCallLog = isCallLog; mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE); @@ -512,8 +553,6 @@ public class CallLogAdapter extends GroupingListAdapter private void findAndCacheViews(View view) { // Get the views to bind to. CallLogListItemViews views = CallLogListItemViews.fromView(view); - views.primaryActionView.setOnClickListener(mActionListener); - views.secondaryActionButtonView.setOnClickListener(mActionListener); view.setTag(views); } @@ -537,36 +576,55 @@ public class CallLogAdapter extends GroupingListAdapter final long duration = c.getLong(CallLogQuery.DURATION); final int callType = c.getInt(CallLogQuery.CALL_TYPE); final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO); + final long rowId = c.getLong(CallLogQuery.ID); + views.rowId = rowId; + + // Store some values used when the actions ViewStub is inflated on expansion of the actions + // section. + views.number = number; + views.numberPresentation = numberPresentation; + views.callType = callType; + views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI); + // Stash away the Ids of the calls so that we can support deleting a row in the call log. + views.callIds = getCallIds(c, rowId, count); final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c); final boolean isVoicemailNumber = PhoneNumberUtilsWrapper.INSTANCE.isVoicemailNumber(number); - // Primary action is always to call, if possible. - if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)) { - // Sets the primary action to call the number. - views.primaryActionView.setTag(IntentProvider.getReturnCallIntentProvider(number)); - } else { - views.primaryActionView.setTag(null); - } + // Where binding and not in the call log, use default behaviour of invoking a call when + // tapping the primary view. + if (!mIsCallLog) { + views.primaryActionView.setOnClickListener(this.mActionListener); - if ( mShowSecondaryActionButton ) { - // Store away the voicemail information so we can play it directly. - if (callType == Calls.VOICEMAIL_TYPE) { - String voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI); - final long rowId = c.getLong(CallLogQuery.ID); - views.secondaryActionButtonView.setTag( - IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri)); + // Set return call intent, otherwise null. + if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)) { + // Sets the primary action to call the number. + views.primaryActionView.setTag(IntentProvider.getReturnCallIntentProvider(number)); } else { - // Store the call details information. - views.secondaryActionButtonView.setTag( - IntentProvider.getCallDetailIntentProvider( - getCursor(), c.getPosition(), c.getLong(CallLogQuery.ID), count)); + // Number is not callable, so hide button. + views.primaryActionView.setTag(null); } } else { - // No action enabled. - views.secondaryActionButtonView.setTag(null); + // In the call log, expand/collapse an actions section for the call log entry when + // the primary view is tapped. + + // TODO: This needs to be changed to do the proper QP open/close animation. + views.primaryActionView.setOnClickListener(this.mExpandCollapseListener); + + // Note: Binding of the action buttons is done as required in configureActionViews + // when the user expands the actions ViewStub. + } + + // Restore expansion state of the row on rebind. Inflate the actions ViewStub if required, + // and set its visibility state accordingly. + if (isExpanded(rowId)) { + // Inflate the view stub if necessary, and wire up the event handlers. + inflateActionViewStub(view); + views.actionsView.setVisibility(View.VISIBLE); + } else if (views.actionsView != null) { + views.actionsView.setVisibility(View.GONE); } // Lookup contacts with this number @@ -631,8 +689,7 @@ public class CallLogAdapter extends GroupingListAdapter final boolean isNew = c.getInt(CallLogQuery.IS_READ) == 0; // New items also use the highlighted version of the text. final boolean isHighlighted = isNew; - mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted, - mShowSecondaryActionButton); + mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted); int contactType = ContactPhotoManager.TYPE_DEFAULT; @@ -668,6 +725,130 @@ public class CallLogAdapter extends GroupingListAdapter bindBadge(view, info, details, callType); } + /** + * Determines if a call log row with the given Id is expanded to show the action buttons or + * not. If the row Id is not yet tracked, add a new entry assuming the row is collapsed. + * @param rowId + * @return + */ + private boolean isExpanded(long rowId) { + if (!mIsExpanded.containsKey(rowId)) { + mIsExpanded.put(rowId, false); + } + + return mIsExpanded.get(rowId); + } + + /** + * Toggles the expansion state tracked for the call log row identified by rowId and returns + * the new expansion state. + * + * @param rowId The row Id associated with the call log row to expand/collapse. + * @return True where the row is now expanded, false otherwise. + */ + private boolean toggleExpansion(long rowId) { + boolean isExpanded = isExpanded(rowId); + + mIsExpanded.put(rowId, !isExpanded); + return !isExpanded; + } + + /** + * Expands or collapses the view containing the CALLBACK, VOICEMAIL and DELETE action buttons. + * + * @param callLogItem The call log entry parent view. + */ + private void expandOrCollapseActions(View callLogItem) { + final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag(); + + // Hide or show the actions view. + boolean expanded = toggleExpansion(views.rowId); + + // Inflate the view stub if necessary, and wire up the event handlers. + inflateActionViewStub(callLogItem); + + if (expanded) { + views.actionsView.setVisibility(View.VISIBLE); + + // Attempt to give accessibility focus to one of the action buttons. + // This ensures that a user realizes the expansion occurred. + // NOTE(tgunn): requestAccessibilityFocus returns true if the requested + // focus was successful. The first successful focus will satisfy the OR + // block and block further attempts to set focus. + boolean focused = views.callBackButtonView.requestAccessibilityFocus() || + views.voicemailButtonView.requestAccessibilityFocus() || + views.deleteButtonView.requestAccessibilityFocus(); + } else { + views.actionsView.setVisibility(View.GONE); + } + } + + /** + * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not + * inflated during initial binding, so click handlers, tags and accessibility text must be set + * here, if necessary. + * + * @param callLogItem The call log list item view. + */ + private void inflateActionViewStub(View callLogItem) { + final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag(); + + ViewStub stub = (ViewStub)callLogItem.findViewById(R.id.call_log_entry_actions_stub); + if (stub != null) { + views.actionsView = stub.inflate(); + } + + if (views.callBackButtonView == null) { + views.callBackButtonView = (TextView)views.actionsView.findViewById(R.id.call_back_action); + } + + if (views.voicemailButtonView == null) { + views.voicemailButtonView = (TextView)views.actionsView.findViewById(R.id.voicemail_action); + } + + if ( views.deleteButtonView == null) { + views.deleteButtonView = (TextView)views.actionsView.findViewById(R.id.delete_action); + } + + bindActionButtons(views); + } + + /*** + * Binds click handlers and intents to the voicemail, delete and callback action buttons. + * + * @param views The call log item views. + */ + private void bindActionButtons(CallLogListItemViews views) { + // Set return call intent, otherwise null. + if (PhoneNumberUtilsWrapper.canPlaceCallsTo(views.number, views.numberPresentation)) { + // Sets the primary action to call the number. + views.callBackButtonView.setTag( + IntentProvider.getReturnCallIntentProvider(views.number)); + views.callBackButtonView.setVisibility(View.VISIBLE); + views.callBackButtonView.setOnClickListener(mActionListener); + } else { + // Number is not callable, so hide button. + views.callBackButtonView.setTag(null); + views.callBackButtonView.setVisibility(View.GONE); + } + + // For voicemail calls, show the "VOICEMAIL" action button; hide otherwise. + if (views.callType == Calls.VOICEMAIL_TYPE) { + views.voicemailButtonView.setOnClickListener(mActionListener); + views.voicemailButtonView.setTag( + IntentProvider.getPlayVoicemailIntentProvider( + views.rowId, views.voicemailUri)); + views.voicemailButtonView.setVisibility(View.VISIBLE); + } else { + views.voicemailButtonView.setTag(null); + views.voicemailButtonView.setVisibility(View.GONE); + } + + views.deleteButtonView.setOnClickListener(this.mDeleteListener); + + mCallLogViewsHelper.setActionContentDescriptions(views); + } + protected void bindBadge(View view, ContactInfo info, PhoneCallDetails details, int callType) { // Do not show badge in call log. @@ -960,4 +1141,76 @@ public class CallLogAdapter extends GroupingListAdapter } return number; } + + /** + * Retrieves the call Ids represented by the current call log row. + * + * @param cursor Call log cursor to retrieve call Ids from. + * @param id Id of the first call of the grouping. + * @param groupSize Number of calls associated with the current call log row. + * @return Array of call Ids. + */ + private long[] getCallIds(final Cursor cursor, final long id, final int groupSize) { + // We want to restore the position in the cursor at the end. + int startingPosition = cursor.getPosition(); + long[] ids = new long[groupSize]; + // Copy the ids of the rows in the group. + for (int index = 0; index < groupSize; ++index) { + ids[index] = cursor.getLong(CallLogQuery.ID); + cursor.moveToNext(); + } + cursor.moveToPosition(startingPosition); + return ids; + } + + /** + * Retrieves an instance of the asynchronous task executor, creating one if required. + * @return The {@link com.android.dialer.util.AsyncTaskExecutor} + */ + private AsyncTaskExecutor getTaskExecutor() { + if (mAsyncTaskExecutor == null) { + mAsyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor(); + } + return mAsyncTaskExecutor; + } + + /** + * Deletes the calls specified in the callIds array, asynchronously. + * + * @param callIds Ids of calls to be deleted. + */ + private void deleteCalls(long[] callIds) { + if (callIds == null) { + return; + } + + // Build comma separated list of ids to delete. + final StringBuilder callIdString = new StringBuilder(); + for (long callId : callIds) { + if (callIdString.length() != 0) { + callIdString.append(","); + } + callIdString.append(callId); + } + + // Perform removal of call log entries asynchronously. + getTaskExecutor().submit(Tasks.REMOVE_CALL_LOG_ENTRIES, + new AsyncTask() { + @Override + public Void doInBackground(Void... params) { + // Issue delete. + mContext.getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL, + Calls._ID + " IN (" + callIdString + ")", null); + return null; + } + + @Override + public void onPostExecute(Void result) { + // Somewhere went wrong: we're going to bail out and show error to users. + Toast.makeText(mContext, R.string.toast_entry_removed, + Toast.LENGTH_SHORT).show(); + } + } + ); + } } diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java index 2ab613646..094815182 100644 --- a/src/com/android/dialer/calllog/CallLogFragment.java +++ b/src/com/android/dialer/calllog/CallLogFragment.java @@ -154,7 +154,7 @@ public class CallLogFragment extends ListFragment String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); mAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this, new ContactInfoHelper( - getActivity(), currentCountryIso), true, true); + getActivity(), currentCountryIso), true); setListAdapter(mAdapter); mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), this, mLogLimit); diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java index a85cd019e..81d1a2711 100644 --- a/src/com/android/dialer/calllog/CallLogListItemHelper.java +++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java @@ -25,6 +25,7 @@ import android.view.View; import com.android.dialer.PhoneCallDetails; import com.android.dialer.PhoneCallDetailsHelper; import com.android.dialer.R; +import com.android.internal.util.CharSequences; /** * Helper class to fill in the views of a call log entry. @@ -56,13 +57,11 @@ import com.android.dialer.R; * @param views the views to populate * @param details the details of a phone call needed to fill in the data * @param isHighlighted whether to use the highlight text for the call - * @param showSecondaryActionButton whether to show the secondary action button or not */ public void setPhoneCallDetails(CallLogListItemViews views, PhoneCallDetails details, - boolean isHighlighted, boolean showSecondaryActionButton) { + boolean isHighlighted) { mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details, isHighlighted); - boolean canPlay = details.callTypes[0] == Calls.VOICEMAIL_TYPE; // Set the accessibility text for the contact badge views.quickContactView.setContentDescription(getContactBadgeDescription(details)); @@ -70,35 +69,25 @@ import com.android.dialer.R; // Set the primary action accessibility description views.primaryActionView.setContentDescription(getCallDescription(details)); - // If secondary action is visible, either show voicemail playback icon, or - // show the "clock" icon corresponding to the call details screen. - if (showSecondaryActionButton) { - if (canPlay) { - // Playback action takes preference. - configurePlaySecondaryAction(views, isHighlighted); - } else { - // Call details is the secondary action. - configureCallDetailsSecondaryAction(views, details); - } - } else { - // No secondary action is to be shown (ie this is likely a PhoneFavoriteFragment) - views.secondaryActionView.setVisibility(View.GONE); - } + // Cache name or number of caller. Used when setting the content descriptions of buttons + // when the actions ViewStub is inflated. + views.nameOrNumber = this.getNameOrNumber(details); } /** - * Sets the secondary action to invoke call details. + * Sets the accessibility descriptions for the action buttons in the action button ViewStub. * - * @param views the views to populate - * @param details the details of a phone call needed to fill in the call details data + * @param views The views associated with the current call log entry. */ - private void configureCallDetailsSecondaryAction(CallLogListItemViews views, - PhoneCallDetails details) { - views.secondaryActionView.setVisibility(View.VISIBLE); - // Use the small dark grey clock icon. - views.secondaryActionButtonView.setImageResource(R.drawable.ic_menu_history_dk); - views.secondaryActionButtonView.setContentDescription( - mResources.getString(R.string.description_call_details)); + public void setActionContentDescriptions(CallLogListItemViews views) { + views.callBackButtonView.setContentDescription( + mResources.getString(R.string.description_call_back_action, views.nameOrNumber)); + + views.voicemailButtonView.setContentDescription( + mResources.getString(R.string.description_voicemail_action, views.nameOrNumber)); + + views.deleteButtonView.setContentDescription( + mResources.getString(R.string.description_delete_action, views.nameOrNumber)); } /** @@ -123,16 +112,11 @@ import com.android.dialer.R; * * The {Caller Information} references the most recent call associated with the caller. * For incoming calls: - * If missed call: Return missed call from {Name/Number} {Call Type} {Call Time}. - * If answered call: Return answered call from {Name/Number} {Call Type} {Call Time}. - * - * For unknown callers, drop the "Return" part, since the call can't be returned: - * If answered unknown: Answered call from {Name/Number} {Call Time}. - * If missed unknown: Missed call from {Name/Number} {Call Time}. + * If missed call: Missed call from {Name/Number} {Call Type} {Call Time}. + * If answered call: Answered call from {Name/Number} {Call Type} {Call Time}. * * For outgoing calls: - * If outgoing: Call {Name/Number] {Call Type}. {Last} called {Call Time}. - * Where {Last} is dropped if the number of calls for the caller is 1. + * If outgoing: Call to {Name/Number] {Call Type} {Call Time}. * * Where: * {Name/Number} is the name or number of the caller (as shown in call log). @@ -140,8 +124,8 @@ import com.android.dialer.R; * {Call Time} is the time since the last call for the contact occurred. * * Examples: - * 3 calls. New Voicemail. Return missed call from Joe Smith mobile 2 hours ago. - * 2 calls. Call John Doe mobile. Last called 1 hour ago. + * 3 calls. New Voicemail. Missed call from Joe Smith mobile 2 hours ago. + * 2 calls. Answered call from John Doe mobile. Last called 1 hour ago. * @param details Details of call. * @return Return call action description. */ @@ -191,43 +175,17 @@ import com.android.dialer.R; */ public int getCallDescriptionStringID(PhoneCallDetails details) { int lastCallType = getLastCallType(details.callTypes); - boolean isNumberCallable = PhoneNumberUtilsWrapper.canPlaceCallsTo(details.number, - details.numberPresentation); - - // Default string to use is "call XYZ..." just in case we manage to fall through. - int stringID = R.string.description_call_last_multiple; - - if (!isNumberCallable) { - // Number isn't callable; this is an incoming call from an unknown caller. - // An uncallable outgoing call wouldn't be in the call log. - - // Voicemail and missed calls are both considered missed. - if (lastCallType == Calls.VOICEMAIL_TYPE || - lastCallType == Calls.MISSED_TYPE) { - stringID = R.string.description_unknown_missed_call; - } else if (lastCallType == Calls.INCOMING_TYPE) { - stringID = R.string.description_unknown_answered_call; - } + int stringID; + + if (lastCallType == Calls.VOICEMAIL_TYPE || lastCallType == Calls.MISSED_TYPE) { + //Message: Missed call from , , . + stringID = R.string.description_incoming_missed_call; + } else if (lastCallType == Calls.INCOMING_TYPE) { + //Message: Answered call from , , . + stringID = R.string.description_incoming_answered_call; } else { - // Known caller, so callable. - - // Missed call (ie voicemail or missed) - if (lastCallType == Calls.VOICEMAIL_TYPE || - lastCallType == Calls.MISSED_TYPE) { - stringID = R.string.description_return_missed_call; - } else if (lastCallType == Calls.INCOMING_TYPE) { - // Incoming answered. - stringID = R.string.description_return_answered_call; - } else { - // Outgoing call. - - // If we have a history of multiple calls - if (details.callTypes.length > 1) { - stringID = R.string.description_call_last_multiple; - } else { - stringID = R.string.description_call_last; - } - } + //Message: Call to , , . + stringID = R.string.description_outgoing_call; } return stringID; } @@ -260,13 +218,4 @@ import com.android.dialer.R; } return recipient; } - - /** Sets the secondary action to correspond to the play button. */ - private void configurePlaySecondaryAction(CallLogListItemViews views, boolean isHighlighted) { - views.secondaryActionView.setVisibility(View.VISIBLE); - views.secondaryActionButtonView.setImageResource( - isHighlighted ? R.drawable.ic_play_active_holo_dark : R.drawable.ic_play_holo_light); - views.secondaryActionButtonView.setContentDescription( - mResources.getString(R.string.description_call_log_play_button)); - } } diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java index a3789569e..879647f15 100644 --- a/src/com/android/dialer/calllog/CallLogListItemViews.java +++ b/src/com/android/dialer/calllog/CallLogListItemViews.java @@ -34,25 +34,67 @@ public final class CallLogListItemViews { public final QuickContactBadge quickContactView; /** The primary action view of the entry. */ public final View primaryActionView; - /** The secondary action view, which includes both the vertical divider line and - * the action button itself. Used so that the button and divider line can be - * made visible/hidden as a whole. */ - public final View secondaryActionView; - /** The secondary action button on the entry. */ - public final ImageView secondaryActionButtonView; /** The details of the phone call. */ public final PhoneCallDetailsViews phoneCallDetailsViews; /** The text of the header of a section. */ public final TextView listHeaderTextView; + /** The view containing call log item actions. Null until the ViewStub is inflated. */ + public View actionsView; + /** The "call back" action button - assigned only when the action section is expanded. */ + public TextView callBackButtonView; + /** The "delete" action button - assigned only when the action section is expanded. */ + public TextView deleteButtonView; + /** The "voicemail" action button - assigned only when the action section is expanded. */ + public TextView voicemailButtonView; + + /** + * The row Id for the first call associated with the call log entry. Used as a key for the + * map used to track which call log entries have the action button section expanded. + */ + public long rowId; + + /** + * The call Ids for the calls represented by the current call log entry. Used when the user + * deletes a call log entry. + */ + public long[] callIds; + + /** + * The callable phone number for the current call log entry. Cached here as the call back + * intent is set only when the actions ViewStub is inflated. + */ + public String number; + + /** + * The phone number presentation for the current call log entry. Cached here as the call back + * intent is set only when the actions ViewStub is inflated. + */ + public int numberPresentation; + + /** + * The type of call for the current call log entry. Cached here as the call back + * intent is set only when the actions ViewStub is inflated. + */ + public int callType; + + /** + * If the call has an associated voicemail message, the URI of the voicemail message for + * playback. Cached here as the voicemail intent is only set when the actions ViewStub is + * inflated. + */ + public String voicemailUri; + + /** + * The name or number associated with the call. Cached here for use when setting content + * descriptions on buttons in the actions ViewStub when it is inflated. + */ + public CharSequence nameOrNumber; private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView, - View secondaryActionView, ImageView secondaryActionButtonView, PhoneCallDetailsViews phoneCallDetailsViews, TextView listHeaderTextView) { this.quickContactView = quickContactView; this.primaryActionView = primaryActionView; - this.secondaryActionView = secondaryActionView; - this.secondaryActionButtonView = secondaryActionButtonView; this.phoneCallDetailsViews = phoneCallDetailsViews; this.listHeaderTextView = listHeaderTextView; } @@ -61,8 +103,6 @@ public final class CallLogListItemViews { return new CallLogListItemViews( (QuickContactBadge) view.findViewById(R.id.quick_contact_photo), view.findViewById(R.id.primary_action_view), - view.findViewById(R.id.secondary_action_view), - (ImageView) view.findViewById(R.id.secondary_action_icon), PhoneCallDetailsViews.fromView(view), (TextView) view.findViewById(R.id.call_log_header)); } @@ -72,8 +112,6 @@ public final class CallLogListItemViews { return new CallLogListItemViews( new QuickContactBadge(context), new View(context), - new View(context), - new ImageView(context), PhoneCallDetailsViews.createForTest(context), new TextView(context)); } diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java index 49d6b3af3..88e5ce0e9 100644 --- a/src/com/android/dialer/list/ListsFragment.java +++ b/src/com/android/dialer/list/ListsFragment.java @@ -177,7 +177,7 @@ public class ListsFragment extends Fragment implements CallLogQueryHandler.Liste this, 1); final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this, - new ContactInfoHelper(getActivity(), currentCountryIso), false, false); + new ContactInfoHelper(getActivity(), currentCountryIso), false); mMergedAdapter = new ShortcutCardsAdapter(getActivity(), this, mCallLogAdapter); } diff --git a/src/com/android/dialerbind/ObjectFactory.java b/src/com/android/dialerbind/ObjectFactory.java index be91e3310..e7ca8d9e3 100644 --- a/src/com/android/dialerbind/ObjectFactory.java +++ b/src/com/android/dialerbind/ObjectFactory.java @@ -39,15 +39,11 @@ public class ObjectFactory { * @param context The context to use. * @param callFetcher Instance of call fetcher to use. * @param contactInfoHelper Instance of contact info helper class to use. - * @param hideSecondaryAction If true, secondary action will be hidden (ie call details - * or play voicemail). * @param isCallLog Is this call log adapter being used on the call log? * @return Instance of CallLogAdapter. */ public static CallLogAdapter newCallLogAdapter(Context context, CallFetcher callFetcher, - ContactInfoHelper contactInfoHelper, boolean hideSecondaryAction, - boolean isCallLog) { - return new CallLogAdapter(context, callFetcher, contactInfoHelper, hideSecondaryAction, - isCallLog); + ContactInfoHelper contactInfoHelper, boolean isCallLog) { + return new CallLogAdapter(context, callFetcher, contactInfoHelper, isCallLog); } } diff --git a/tests/src/com/android/dialer/PhoneCallDetailsHelperTest.java b/tests/src/com/android/dialer/PhoneCallDetailsHelperTest.java index 9b7d9de13..626ff1686 100644 --- a/tests/src/com/android/dialer/PhoneCallDetailsHelperTest.java +++ b/tests/src/com/android/dialer/PhoneCallDetailsHelperTest.java @@ -22,6 +22,7 @@ import android.provider.CallLog.Calls; import android.test.AndroidTestCase; import android.text.Html; import android.text.Spanned; +import android.util.Log; import android.view.View; import android.widget.TextView; @@ -111,9 +112,9 @@ public class PhoneCallDetailsHelperTest extends AndroidTestCase { public void testSetPhoneCallDetails_Normal() { setPhoneCallDetailsWithNumber("14125551212", Calls.PRESENTATION_ALLOWED, "1-412-555-1212"); - assertEquals("Yesterday", mViews.callTypeAndDate.getText().toString()); - assertEqualsHtml("Yesterday", - mViews.callTypeAndDate.getText()); + assertTrue(mViews.callLocationAndDate.getText().toString().contains("Yesterday")); + assertTrue(mViews.callLocationAndDate.getText().toString().contains( + "Yesterday")); } /** Asserts that a char sequence is actually a Spanned corresponding to the expected HTML. */ @@ -252,12 +253,12 @@ public class PhoneCallDetailsHelperTest extends AndroidTestCase { /** Asserts that the label text field contains the given string value. */ private void assertLabelEquals(String text) { - assertEquals(text, mViews.labelView.getText().toString()); + assertTrue(mViews.callLocationAndDate.getText().toString().contains(text)); } /** Asserts that the date text field contains the given string value. */ private void assertDateEquals(String text) { - assertEquals(text, mViews.callTypeAndDate.getText().toString()); + assertTrue(mViews.callLocationAndDate.getText().toString().contains(text)); } /** Asserts that the call type contains the images with the given drawables. */ @@ -268,7 +269,7 @@ public class PhoneCallDetailsHelperTest extends AndroidTestCase { assertEquals(id, mViews.callTypeIcons.getCallType(index)); } assertEquals(View.VISIBLE, mViews.callTypeIcons.getVisibility()); - assertEquals("Yesterday", mViews.callTypeAndDate.getText().toString()); + assertTrue(mViews.callLocationAndDate.getText().toString().contains("Yesterday")); } /** @@ -282,7 +283,8 @@ public class PhoneCallDetailsHelperTest extends AndroidTestCase { assertEquals(id, mViews.callTypeIcons.getCallType(index)); } assertEquals(View.VISIBLE, mViews.callTypeIcons.getVisibility()); - assertEquals(overflowText + " Yesterday", mViews.callTypeAndDate.getText().toString()); + assertTrue(mViews.callLocationAndDate.getText().toString().contains(overflowText)); + assertTrue(mViews.callLocationAndDate.getText().toString().contains("Yesterday")); } /** Sets the phone call details with default values and the given number. */ diff --git a/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java b/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java index 12cdb2b99..55e422498 100644 --- a/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java +++ b/tests/src/com/android/dialer/calllog/CallLogAdapterTest.java @@ -212,7 +212,7 @@ public class CallLogAdapterTest extends AndroidTestCase { public TestCallLogAdapter(Context context, CallFetcher callFetcher, ContactInfoHelper contactInfoHelper) { - super(context, callFetcher, contactInfoHelper, false, false); + super(context, callFetcher, contactInfoHelper, false); } @Override diff --git a/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java b/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java index 25f53790b..a423cde21 100644 --- a/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java +++ b/tests/src/com/android/dialer/calllog/CallLogFragmentTest.java @@ -35,6 +35,7 @@ import android.telephony.TelephonyManager; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; import android.view.View; import android.widget.FrameLayout; @@ -174,7 +175,6 @@ public class CallLogFragmentTest extends ActivityInstrumentationTestCase2