diff options
-rw-r--r-- | res/layout/call_log_fragment.xml | 6 | ||||
-rw-r--r-- | res/layout/call_log_list_item.xml | 33 | ||||
-rw-r--r-- | res/values/dimens.xml | 2 | ||||
-rw-r--r-- | res/values/strings.xml | 11 | ||||
-rw-r--r-- | src/com/android/dialer/calllog/CallLogAdapter.java | 117 | ||||
-rw-r--r-- | src/com/android/dialer/calllog/CallLogGroupBuilder.java | 98 | ||||
-rw-r--r-- | src/com/android/dialer/calllog/CallLogListItemViews.java | 17 |
7 files changed, 250 insertions, 34 deletions
diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml index b4714a333..5cddbe749 100644 --- a/res/layout/call_log_fragment.xml +++ b/res/layout/call_log_fragment.xml @@ -61,13 +61,17 @@ <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> + <!-- clipChildren=true is required to ensure shadows on elevated call log entries are not + clipped.--> <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" android:fadingEdge="none" android:scrollbarStyle="outsideOverlay" android:divider="@null" - android:nestedScrollingEnabled="true" /> + android:nestedScrollingEnabled="true" + android:clipChildren="false" + /> <TextView android:id="@android:id/empty" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml index 6068bd817..b36101e55 100644 --- a/res/layout/call_log_list_item.xml +++ b/res/layout/call_log_list_item.xml @@ -22,20 +22,23 @@ android:id="@+id/call_log_list_item" android:orientation="vertical" > - <!-- - This layout may represent either a call log item or one of the - headers in the call log. - - The former will make the @id/call_log_item visible and the - @id/call_log_header gone. - - The latter will make the @id/call_log_header visible and the - @id/call_log_item gone - --> - + <!-- Day group heading. Used to show a "today", "yesterday", "last week" or "other" heading + above a group of call log entries. --> + <TextView + android:id="@+id/call_log_day_group_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/call_log_outer_margin" + android:layout_marginEnd="@dimen/call_log_outer_margin" + android:textColor="?attr/call_log_secondary_text_color" + android:textSize="@dimen/call_log_secondary_text_size" + android:paddingTop="@dimen/call_log_day_group_padding" + android:paddingBottom="0dp" + /> <!-- Linear layout to separate the primary area containing the contact badge and caller information and the secondary action (call details / play voicemail). --> <LinearLayout + android:id="@+id/call_log_row" android:layout_width="match_parent" android:layout_height="wrap_content" android:baselineAligned="false" @@ -127,14 +130,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> - <TextView - android:id="@+id/call_log_header" - style="@style/ContactListSeparatorTextViewStyle" - android:layout_marginStart="@dimen/call_log_outer_margin" - android:layout_marginEnd="@dimen/call_log_outer_margin" - android:paddingTop="@dimen/call_log_inner_margin" - android:paddingBottom="@dimen/call_log_inner_margin" /> - <!-- Displays the extra link section --> <ViewStub android:id="@+id/link_stub" android:layout="@layout/call_log_list_item_extra" diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 89bd5927b..ae653303c 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -130,4 +130,6 @@ <dimen name="call_log_action_height">48dp</dimen> <!-- Elevation of expanded call log items. --> <dimen name="call_log_expanded_elevation">4dp</dimen> + <!-- Padding above call log day group headers. --> + <dimen name="call_log_day_group_padding">16dp</dimen> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index d6d4766fe..4215f5367 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -718,6 +718,15 @@ [CHAR LIMIT=NONE] --> <string name="description_delete_action">Delete call log entry for <xliff:g id="nameOrNumber" example="John Smith">%1$s</xliff:g></string> - <!-- Toast message which appears when a call log entry is deleted. --> + <!-- Toast message which appears when a call log entry is deleted. + [CHAR LIMIT=NONE] --> <string name="toast_entry_removed">Call log entry deleted.</string> + + <!-- String used as a header in the call log above calls which occurred last week. + [CHAR LIMIT=65] --> + <string name="call_log_header_last_week">Last week</string> + + <!-- String used as a header in the call log above calls which ocurred more than a week ago. + [CHAR LIMIT=65] --> + <string name="call_log_header_other">Other</string> </resources> diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index 0aca9136b..c4389ad49 100644 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -40,6 +40,7 @@ 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.DateUtils; import com.android.contacts.common.util.UriUtils; import com.android.dialer.PhoneCallDetails; import com.android.dialer.PhoneCallDetailsHelper; @@ -60,7 +61,6 @@ 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, @@ -117,6 +117,12 @@ public class CallLogAdapter extends GroupingListAdapter /** The size of the cache of contact info. */ private static final int CONTACT_INFO_CACHE_SIZE = 100; + /** Localized string representing the word "Today". */ + private static final CharSequence TODAY_LABEL = DateUtils.getTodayString(); + + /** Localized string representing the word "Yesterday". */ + private static final CharSequence YESTERDAY_LABEL = DateUtils.getYesterdayString(); + protected final Context mContext; private final ContactInfoHelper mContactInfoHelper; private final CallFetcher mCallFetcher; @@ -139,6 +145,20 @@ public class CallLogAdapter extends GroupingListAdapter private HashMap<Long,Boolean> mIsExpanded = new HashMap<Long,Boolean>(); /** + * Hashmap, keyed by call Id, used to track the day group for a call. As call log entries are + * put into the primary call groups in {@link com.android.dialer.calllog.CallLogGroupBuilder}, + * they are also assigned a secondary "day group". This hashmap tracks the day group assigned + * to all calls in the call log. This information is used to trigger the display of a day + * group header above the call log entry at the start of a day group. + * Note: Multiple calls are grouped into a single primary "call group" in the call log, and + * the cursor used to bind rows includes all of these calls. When determining if a day group + * change has occurred it is necessary to look at the last entry in the call log to determine + * its day group. This hashmap provides a means of determining the previous day group without + * having to reverse the cursor to the start of the previous day call log entry. + */ + private HashMap<Long,Integer> mDayGroups = new HashMap<Long, Integer>(); + + /** * A request for contact details for the given number. */ private static final class ContactInfoRequest { @@ -589,7 +609,6 @@ public class CallLogAdapter extends GroupingListAdapter // Default case: an item in the call log. views.primaryActionView.setVisibility(View.VISIBLE); - views.listHeaderTextView.setVisibility(View.GONE); final String number = c.getString(CallLogQuery.NUMBER); final int numberPresentation = c.getInt(CallLogQuery.NUMBER_PRESENTATION); @@ -600,6 +619,21 @@ public class CallLogAdapter extends GroupingListAdapter final long rowId = c.getLong(CallLogQuery.ID); views.rowId = rowId; + // For entries in the call log, check if the day group has changed and display a header + // if necessary. + if (mIsCallLog) { + int currentGroup = getDayGroupForCall(rowId); + int previousGroup = getPreviousDayGroup(c); + if (currentGroup != previousGroup) { + views.dayGroupHeader.setVisibility(View.VISIBLE); + views.dayGroupHeader.setText(getGroupDescription(currentGroup)); + } else { + views.dayGroupHeader.setVisibility(View.GONE); + } + } else { + views.dayGroupHeader.setVisibility(View.GONE); + } + // Store some values used when the actions ViewStub is inflated on expansion of the actions // section. views.number = number; @@ -738,6 +772,38 @@ public class CallLogAdapter extends GroupingListAdapter } /** + * Retrieves the day group of the previous call in the call log. Used to determine if the day + * group has changed and to trigger display of the day group text. + * + * @param cursor The call log cursor. + * @return The previous day group, or DAY_GROUP_NONE if this is the first call. + */ + private int getPreviousDayGroup(Cursor cursor) { + // We want to restore the position in the cursor at the end. + int startingPosition = cursor.getPosition(); + int dayGroup = CallLogGroupBuilder.DAY_GROUP_NONE; + if (cursor.moveToPrevious()) { + long previousRowId = cursor.getLong(CallLogQuery.ID); + dayGroup = getDayGroupForCall(previousRowId); + } + cursor.moveToPosition(startingPosition); + return dayGroup; + } + + /** + * Given a call Id, look up the day group that the call belongs to. The day group data is + * populated in {@link com.android.dialer.calllog.CallLogGroupBuilder}. + * + * @param callId The call to retrieve the day group for. + * @return The day group for the call. + */ + private int getDayGroupForCall(long callId) { + if (mDayGroups.containsKey(callId)) { + return mDayGroups.get(callId); + } + return CallLogGroupBuilder.DAY_GROUP_NONE; + } + /** * 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 @@ -779,9 +845,9 @@ public class CallLogAdapter extends GroupingListAdapter inflateActionViewStub(callLogItem); views.actionsView.setVisibility(View.VISIBLE); - callLogItem.setBackgroundColor( + views.callLogEntryView.setBackgroundColor( callLogItem.getResources().getColor(R.color.background_dialer_light)); - callLogItem.setElevation( + views.callLogEntryView.setElevation( callLogItem.getResources().getDimension(R.dimen.call_log_expanded_elevation)); // Attempt to give accessibility focus to one of the action buttons. @@ -799,9 +865,9 @@ public class CallLogAdapter extends GroupingListAdapter views.actionsView.setVisibility(View.GONE); } - callLogItem.setBackgroundColor( + views.callLogEntryView.setBackgroundColor( callLogItem.getResources().getColor(R.color.background_dialer_list_items)); - callLogItem.setElevation(0); + views.callLogEntryView.setElevation(0); } } @@ -1138,6 +1204,27 @@ public class CallLogAdapter extends GroupingListAdapter super.addGroup(cursorPosition, size, expanded); } + /** + * Stores the day group associated with a call in the call log. + * + * @param rowId The row Id of the current call. + * @param dayGroup The day group the call belongs in. + */ + @Override + public void setDayGroup(long rowId, int dayGroup) { + if (!mDayGroups.containsKey(rowId)) { + mDayGroups.put(rowId, dayGroup); + } + } + + /** + * Clears the day group associations on re-bind of the call log. + */ + @Override + public void clearDayGroups() { + mDayGroups.clear(); + } + /* * Get the number from the Contacts, if available, since sometimes * the number provided by caller id may not be formatted properly @@ -1199,6 +1286,24 @@ public class CallLogAdapter extends GroupingListAdapter } /** + * Determines the description for a day group. + * + * @param group The day group to retrieve the description for. + * @return The day group description. + */ + private CharSequence getGroupDescription(int group) { + if (group == CallLogGroupBuilder.DAY_GROUP_TODAY) { + return TODAY_LABEL; + } else if (group == CallLogGroupBuilder.DAY_GROUP_YESTERDAY) { + return YESTERDAY_LABEL; + } else if (group == CallLogGroupBuilder.DAY_GROUP_LAST_WEEK) { + return mContext.getResources().getString(R.string.call_log_header_last_week); + } else { + return mContext.getResources().getString(R.string.call_log_header_other); + } + } + + /** * Retrieves an instance of the asynchronous task executor, creating one if required. * @return The {@link com.android.dialer.util.AsyncTaskExecutor} */ diff --git a/src/com/android/dialer/calllog/CallLogGroupBuilder.java b/src/com/android/dialer/calllog/CallLogGroupBuilder.java index 0b2edf0bb..50cf054a2 100644 --- a/src/com/android/dialer/calllog/CallLogGroupBuilder.java +++ b/src/com/android/dialer/calllog/CallLogGroupBuilder.java @@ -19,22 +19,74 @@ package com.android.dialer.calllog; import android.database.Cursor; import android.provider.CallLog.Calls; import android.telephony.PhoneNumberUtils; +import android.text.format.Time; import com.android.common.widget.GroupingListAdapter; +import com.android.contacts.common.util.DateUtils; import com.android.contacts.common.util.PhoneNumberHelper; import com.google.common.annotations.VisibleForTesting; /** - * Groups together calls in the call log. + * Groups together calls in the call log. The primary grouping attempts to group together calls + * to and from the same number into a single row on the call log. + * A secondary grouping assigns calls, grouped via the primary grouping, to "day groups". The day + * groups provide a means of identifying the calls which occurred "Today", "Yesterday", "Last week", + * or "Other". * <p> * This class is meant to be used in conjunction with {@link GroupingListAdapter}. */ public class CallLogGroupBuilder { public interface GroupCreator { + + /** + * Defines the interface for adding a group to the call log. + * The primary group for a call log groups the calls together based on the number which was + * dialed. + * @param cursorPosition The starting position of the group in the cursor. + * @param size The size of the group. + * @param expanded Whether the group is expanded; always false for the call log. + */ public void addGroup(int cursorPosition, int size, boolean expanded); + + /** + * Defines the interface for tracking the day group each call belongs to. Calls in a call + * group are assigned the same day group as the first call in the group. The day group + * assigns calls to the buckets: Today, Yesterday, Last week, and Other + * + * @param rowId The row Id of the current call. + * @param dayGroup The day group the call belongs in. + */ + public void setDayGroup(long rowId, int dayGroup); + + /** + * Defines the interface for clearing the day groupings information on rebind/regroup. + */ + public void clearDayGroups(); } + /** + * Day grouping for call log entries used to represent no associated day group. Used primarily + * when retrieving the previous day group, but there is no previous day group (i.e. we are at + * the start of the list). + */ + public static final int DAY_GROUP_NONE = -1; + + /** Day grouping for calls which occurred today. */ + public static final int DAY_GROUP_TODAY = 0; + + /** Day grouping for calls which occurred yesterday. */ + public static final int DAY_GROUP_YESTERDAY = 1; + + /** Day grouping for calls which occurred last week. */ + public static final int DAY_GROUP_LAST_WEEK = 2; + + /** Day grouping for calls which occurred before last week. */ + public static final int DAY_GROUP_OTHER = 3; + + /** Instance of the time object used for time calculations. */ + private static final Time TIME = new Time(); + /** The object on which the groups are created. */ private final GroupCreator mGroupCreator; @@ -59,18 +111,33 @@ public class CallLogGroupBuilder { return; } + // Clear any previous day grouping information. + mGroupCreator.clearDayGroups(); + + // Get current system time, used for calculating which day group calls belong to. + long currentTime = System.currentTimeMillis(); + int currentGroupSize = 1; cursor.moveToFirst(); // The number of the first entry in the group. String firstNumber = cursor.getString(CallLogQuery.NUMBER); // This is the type of the first call in the group. int firstCallType = cursor.getInt(CallLogQuery.CALL_TYPE); + + // Determine the day group for the first call in the cursor. + final long firstDate = cursor.getLong(CallLogQuery.DATE); + final long firstRowId = cursor.getLong(CallLogQuery.ID); + int currentGroupDayGroup = getDayGroup(firstDate, currentTime); + mGroupCreator.setDayGroup(firstRowId, currentGroupDayGroup); + while (cursor.moveToNext()) { // The number of the current row in the cursor. final String currentNumber = cursor.getString(CallLogQuery.NUMBER); final int callType = cursor.getInt(CallLogQuery.CALL_TYPE); final boolean sameNumber = equalNumbers(firstNumber, currentNumber); final boolean shouldGroup; + final long currentCallId = cursor.getLong(CallLogQuery.ID); + final long date = cursor.getLong(CallLogQuery.DATE); if (!sameNumber) { // Should only group with calls from the same number. @@ -88,6 +155,11 @@ public class CallLogGroupBuilder { // the group until we find a call that does not match. currentGroupSize++; } else { + // The call group has changed, so determine the day group for the new call group. + // This ensures all calls grouped together in the call log are assigned the same + // day group. + currentGroupDayGroup = getDayGroup(date, currentTime); + // Create a group for the previous set of calls, excluding the current one, but do // not create a group for a single call. if (currentGroupSize > 1) { @@ -99,6 +171,9 @@ public class CallLogGroupBuilder { firstNumber = currentNumber; firstCallType = callType; } + + // Save the day group associated with the current call. + mGroupCreator.setDayGroup(currentCallId, currentGroupDayGroup); } // If the last set of calls at the end of the call log was itself a group, create it now. if (currentGroupSize > 1) { @@ -154,4 +229,25 @@ public class CallLogGroupBuilder { return userinfo1.equals(userinfo2) && rest1.equalsIgnoreCase(rest2); } + + /** + * Given a call date and the current date, determine which date group the call belongs in. + * + * @param date The call date. + * @param now The current date. + * @return The date group the call belongs in. + */ + private int getDayGroup(long date, long now) { + int days = DateUtils.getDayDifference(TIME, date, now); + + if (days == 0) { + return DAY_GROUP_TODAY; + } else if (days == 1) { + return DAY_GROUP_YESTERDAY; + } else if (days > 1 && days <=7) { + return DAY_GROUP_LAST_WEEK; + } else { + return DAY_GROUP_OTHER; + } + } } diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java index 333769d7e..648362e09 100644 --- a/src/com/android/dialer/calllog/CallLogListItemViews.java +++ b/src/com/android/dialer/calllog/CallLogListItemViews.java @@ -36,8 +36,10 @@ public final class CallLogListItemViews { public final View primaryActionView; /** The details of the phone call. */ public final PhoneCallDetailsViews phoneCallDetailsViews; - /** The text of the header of a section. */ - public final TextView listHeaderTextView; + /** The text of the header for a day grouping. */ + public final TextView dayGroupHeader; + /** The view containing the details for the call log row, including the action buttons. */ + public final View callLogEntryView; /** 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. */ @@ -91,12 +93,13 @@ public final class CallLogListItemViews { public CharSequence nameOrNumber; private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView, - PhoneCallDetailsViews phoneCallDetailsViews, - TextView listHeaderTextView) { + PhoneCallDetailsViews phoneCallDetailsViews, View callLogEntryView, + TextView dayGroupHeader) { this.quickContactView = quickContactView; this.primaryActionView = primaryActionView; this.phoneCallDetailsViews = phoneCallDetailsViews; - this.listHeaderTextView = listHeaderTextView; + this.callLogEntryView = callLogEntryView; + this.dayGroupHeader = dayGroupHeader; } public static CallLogListItemViews fromView(View view) { @@ -104,7 +107,8 @@ public final class CallLogListItemViews { (QuickContactBadge) view.findViewById(R.id.quick_contact_photo), view.findViewById(R.id.primary_action_view), PhoneCallDetailsViews.fromView(view), - (TextView) view.findViewById(R.id.call_log_header)); + view.findViewById(R.id.call_log_row), + (TextView) view.findViewById(R.id.call_log_day_group_label)); } @NeededForTesting @@ -113,6 +117,7 @@ public final class CallLogListItemViews { new QuickContactBadge(context), new View(context), PhoneCallDetailsViews.createForTest(context), + new View(context), new TextView(context)); views.callBackButtonView = new TextView(context); views.deleteButtonView = new TextView(context); |