Merge "Split new and older items in the call log."
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index 7e82b40..61b7340 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -16,11 +16,41 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="?attr/call_log_list_item_height"
+ android:layout_height="wrap_content"
>
+ <!--
+ This layout may represent either a call log item (but not a group thereof) or one of the
+ headers in the call log.
- <include layout="@layout/call_log_contact_photo"/>
- <include layout="@layout/call_log_action_call"/>
- <include layout="@layout/call_log_list_item_layout"/>
+ 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
+ -->
+
+ <RelativeLayout
+ android:id="@+id/call_log_item"
+ android:layout_width="fill_parent"
+ android:layout_height="?attr/call_log_list_item_height"
+ >
+ <include layout="@layout/call_log_contact_photo"/>
+ <include layout="@layout/call_log_action_call"/>
+ <include layout="@layout/call_log_list_item_layout"/>
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/call_log_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/call_log_list_header_background"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="20dip"
+ >
+ <TextView
+ android:id="@+id/call_log_header_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="?attr/call_log_list_header_text_color"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ />
+ </LinearLayout>
</RelativeLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5641d35..f6591f3 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -57,4 +57,10 @@
<!-- Color of the text in the updates tab in the tab carousel on the contact detail page -->
<color name="detail_update_tab_text_color">#777777</color>
+
+ <!-- Color of the text describing an unconsumed missed call. -->
+ <color name="call_log_missed_call_highlight_color">#FF0000</color>
+
+ <!-- Color of the text describing an unconsumed voicemail. -->
+ <color name="call_log_voicemail_highlight_color">#0000FF</color>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9e2e085..0e56ef4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1607,4 +1607,10 @@
<!-- The separator between the call type text and the date in the call log [CHAR LIMIT=3] -->
<string name="call_log_type_date_separator">/</string>
+
+ <!-- The header in the call log used to identify missed calls and voicemail that have not yet been consumed [CHAR LIMIT=10] -->
+ <string name="call_log_new_header">New</string>
+
+ <!-- The header in the call log used to identify items that have been already consumed [CHAR LIMIT=10] -->
+ <string name="call_log_old_header">Older</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ff8f841..4638b2e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -41,6 +41,8 @@
<item name="call_log_list_contact_photo_size">60dip</item>
<item name="call_log_list_contact_photo_margin">5dip</item>
<item name="call_log_list_item_height">70dip</item>
+ <item name="call_log_list_header_text_color">#AAAAFF</item>
+ <item name="call_log_list_header_background">@drawable/call_log_action_bar_bg</item>
<!-- CallLog -->
<item name="call_log_date_margin">5dip</item>
<item name="call_log_primary_text_color">#FFFFFF</item>
@@ -155,6 +157,8 @@
<attr name="call_log_list_contact_photo_size" format="dimension" />
<attr name="call_log_list_contact_photo_margin" format="dimension" />
<attr name="call_log_list_item_height" format="dimension" />
+ <attr name="call_log_list_header_text_color" format="color" />
+ <attr name="call_log_list_header_background" format="reference" />
</declare-styleable>
<style name="PeopleTheme" parent="android:Theme.Holo.Light.SplitActionBarWhenNarrow">
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 5f55f80..cce48dd 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -224,7 +224,7 @@
// Set the details header, based on the first phone call.
mPhoneCallDetailsHelper.setPhoneCallDetails(mPhoneCallDetailsViews,
- details[0], false);
+ details[0], false, false);
// Cache the details about the phone number.
final Uri numberCallUri = mPhoneNumberHelper.getCallUri(mNumber);
diff --git a/src/com/android/contacts/PhoneCallDetailsHelper.java b/src/com/android/contacts/PhoneCallDetailsHelper.java
index 6bdfbaa..e115d21 100644
--- a/src/com/android/contacts/PhoneCallDetailsHelper.java
+++ b/src/com/android/contacts/PhoneCallDetailsHelper.java
@@ -60,7 +60,7 @@
/** Fills the call details views with content. */
public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details,
- boolean useIcons) {
+ boolean useIcons, boolean isHighlighted) {
if (useIcons) {
views.callTypeIcons.removeAllViews();
int count = details.callTypes.length;
@@ -77,7 +77,9 @@
// Use the name of the first call type.
// TODO: We should update this to handle the text for multiple calls as well.
int callType = details.callTypes[0];
- views.callTypeText.setText(mCallTypeHelper.getCallTypeText(callType));
+ views.callTypeText.setText(
+ isHighlighted ? mCallTypeHelper.getHighlightedCallTypeText(callType)
+ : mCallTypeHelper.getCallTypeText(callType));
views.callTypeIcons.removeAllViews();
views.callTypeText.setVisibility(View.VISIBLE);
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 90e405a..8996821 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -16,6 +16,7 @@
package com.android.contacts.calllog;
+import com.android.common.io.MoreCloseables;
import com.android.common.widget.GroupingListAdapter;
import com.android.contacts.CallDetailActivity;
import com.android.contacts.ContactPhotoManager;
@@ -38,6 +39,8 @@
import android.content.res.Resources;
import android.database.CharArrayBuffer;
import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteDiskIOException;
import android.database.sqlite.SQLiteException;
@@ -70,6 +73,8 @@
import java.lang.ref.WeakReference;
import java.util.LinkedList;
+import javax.annotation.concurrent.GuardedBy;
+
/**
* Displays a list of call log entries.
*/
@@ -80,21 +85,39 @@
private static final int CONTACT_INFO_CACHE_SIZE = 100;
/** The query for the call log table */
- private static final class CallLogQuery {
+ public static final class CallLogQuery {
public static final String[] _PROJECTION = new String[] {
Calls._ID,
Calls.NUMBER,
Calls.DATE,
Calls.DURATION,
Calls.TYPE,
- Calls.COUNTRY_ISO};
-
+ Calls.COUNTRY_ISO,
+ };
public static final int ID = 0;
public static final int NUMBER = 1;
public static final int DATE = 2;
public static final int DURATION = 3;
public static final int CALL_TYPE = 4;
public static final int COUNTRY_ISO = 5;
+
+ /**
+ * The name of the synthetic "section" column.
+ * <p>
+ * This column identifies whether a row is a header or an actual item, and whether it is
+ * part of the new or old calls.
+ */
+ public static final String SECTION_NAME = "section";
+ /** The index of the "section" column in the projection. */
+ public static final int SECTION = 6;
+ /** The value of the "section" column for the header of the new section. */
+ public static final int SECTION_NEW_HEADER = 0;
+ /** The value of the "section" column for the items of the new section. */
+ public static final int SECTION_NEW_ITEM = 1;
+ /** The value of the "section" column for the header of the old section. */
+ public static final int SECTION_OLD_HEADER = 2;
+ /** The value of the "section" column for the items of the old section. */
+ public static final int SECTION_OLD_ITEM = 3;
}
/** The query to use for the phones table */
@@ -123,9 +146,6 @@
public static final int DELETE_ALL = 1;
}
- private static final int QUERY_TOKEN = 53;
- private static final int UPDATE_TOKEN = 54;
-
private CallLogAdapter mAdapter;
private QueryHandler mQueryHandler;
private String mVoiceMailNumber;
@@ -499,7 +519,6 @@
@Override
protected void addGroups(Cursor cursor) {
-
int count = cursor.getCount();
if (count == 0) {
return;
@@ -520,7 +539,8 @@
// Group adjacent calls with the same number. Make an exception
// for the latest item if it was a missed call. We don't want
// a missed call to be hidden inside a group.
- if (sameNumber && currentCallType != Calls.MISSED_TYPE) {
+ if (sameNumber && currentCallType != Calls.MISSED_TYPE
+ && !isSectionHeader(cursor)) {
groupItemCount++;
} else {
if (groupItemCount > 1) {
@@ -549,6 +569,18 @@
}
}
+ private boolean isSectionHeader(Cursor cursor) {
+ int section = cursor.getInt(CallLogQuery.SECTION);
+ return section == CallLogQuery.SECTION_NEW_HEADER
+ || section == CallLogQuery.SECTION_OLD_HEADER;
+ }
+
+ private boolean isNewSection(Cursor cursor) {
+ int section = cursor.getInt(CallLogQuery.SECTION);
+ return section == CallLogQuery.SECTION_NEW_ITEM
+ || section == CallLogQuery.SECTION_NEW_HEADER;
+ }
+
protected boolean equalPhoneNumbers(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
// TODO add PhoneNumberUtils.compare(CharSequence, CharSequence) to avoid
@@ -625,12 +657,32 @@
*/
private void bindView(View view, Cursor c, int count) {
final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ final int section = c.getInt(CallLogQuery.SECTION);
- String number = c.getString(CallLogQuery.NUMBER);
- long date = c.getLong(CallLogQuery.DATE);
- long duration = c.getLong(CallLogQuery.DURATION);
+ if (views.standAloneItemView != null) {
+ // This is stand-alone item: it might, however, be a header: check the value of the
+ // section column in the cursor.
+ if (section == CallLogQuery.SECTION_NEW_HEADER
+ || section == CallLogQuery.SECTION_OLD_HEADER) {
+ views.standAloneItemView.setVisibility(View.GONE);
+ views.standAloneHeaderView.setVisibility(View.VISIBLE);
+ views.standAloneHeaderTextView.setText(
+ section == CallLogQuery.SECTION_NEW_HEADER
+ ? R.string.call_log_new_header
+ : R.string.call_log_old_header);
+ // Nothing else to set up for a header.
+ return;
+ }
+ // Default case: an item in the call log.
+ views.standAloneItemView.setVisibility(View.VISIBLE);
+ views.standAloneHeaderView.setVisibility(View.GONE);
+ }
+
+ final String number = c.getString(CallLogQuery.NUMBER);
+ final long date = c.getLong(CallLogQuery.DATE);
+ final long duration = c.getLong(CallLogQuery.DURATION);
final String formattedNumber;
- String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
+ final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
// Store away the number so we can call it directly if you click on the call icon
if (views.callView != null) {
views.callView.setTag(number);
@@ -673,19 +725,19 @@
formattedNumber = formatPhoneNumber(number, null, countryIso);
}
- long personId = info.personId;
- String name = info.name;
- int ntype = info.type;
- String label = info.label;
- long photoId = info.photoId;
- String lookupKey = info.lookupKey;
+ final long personId = info.personId;
+ final String name = info.name;
+ final int ntype = info.type;
+ final String label = info.label;
+ final long photoId = info.photoId;
+ final String lookupKey = info.lookupKey;
// Assumes the call back feature is on most of the
// time. For private and unknown numbers: hide it.
if (views.callView != null) {
views.callView.setVisibility(View.VISIBLE);
}
- int[] callTypes = getCallTypes(c, count);
+ final int[] callTypes = getCallTypes(c, count);
final PhoneCallDetails details;
if (TextUtils.isEmpty(name)) {
details = new PhoneCallDetails(number, formattedNumber, callTypes, date, duration);
@@ -693,7 +745,13 @@
details = new PhoneCallDetails(number, formattedNumber, callTypes, date, duration,
name, ntype, label, personId, photoId);
}
- mCallLogViewsHelper.setPhoneCallDetails(views, details , true);
+
+ final boolean isNew = isNewSection(c);
+ // Use icons for old items, but text for new ones.
+ final boolean useIcons = !isNew;
+ // New items also use the highlighted version of the text.
+ final boolean isHighlighted = isNew;
+ mCallLogViewsHelper.setPhoneCallDetails(views, details, useIcons, isHighlighted);
if (views.photoView != null) {
bindQuickContact(views.photoView, photoId, personId, lookupKey);
}
@@ -750,9 +808,22 @@
}
}
+ /** Handles asynchronous queries to the call log. */
private static final class QueryHandler extends AsyncQueryHandler {
+ /** The token for the query to fetch the new entries from the call log. */
+ private static final int QUERY_NEW_CALLS_TOKEN = 53;
+ /** The token for the query to fetch the old entries from the call log. */
+ private static final int QUERY_OLD_CALLS_TOKEN = 54;
+ /** The token for the query to mark all missed calls as old after seeing the call log. */
+ private static final int UPDATE_MISSED_CALLS_TOKEN = 55;
+
private final WeakReference<CallLogFragment> mFragment;
+ /** The cursor containing the new calls, or null if they have not yet been fetched. */
+ @GuardedBy("this") private Cursor mNewCallsCursor;
+ /** The cursor containing the old calls, or null if they have not yet been fetched. */
+ @GuardedBy("this") private Cursor mOldCallsCursor;
+
/**
* Simple handler that wraps background calls to catch
* {@link SQLiteException}, such as when the disk is full.
@@ -788,14 +859,173 @@
mFragment = new WeakReference<CallLogFragment>(fragment);
}
+ /** Returns the list of columns for the headers. */
+ private String[] getHeaderColumns() {
+ int length = CallLogQuery._PROJECTION.length;
+ String[] columns = new String[length + 1];
+ System.arraycopy(CallLogQuery._PROJECTION, 0, columns, 0, length);
+ columns[length] = CallLogQuery.SECTION_NAME;
+ return columns;
+ }
+
+ /** Creates a cursor that contains a single row and maps the section to the given value. */
+ private Cursor createHeaderCursorFor(int section) {
+ MatrixCursor matrixCursor = new MatrixCursor(getHeaderColumns());
+ matrixCursor.addRow(new Object[]{ -1L, "", 0L, 0L, 0, "", section });
+ return matrixCursor;
+ }
+
+ /** Returns a cursor for the old calls header. */
+ private Cursor createOldCallsHeaderCursor() {
+ return createHeaderCursorFor(CallLogQuery.SECTION_OLD_HEADER);
+ }
+
+ /** Returns a cursor for the new calls header. */
+ private Cursor createNewCallsHeaderCursor() {
+ return createHeaderCursorFor(CallLogQuery.SECTION_NEW_HEADER);
+ }
+
+ /**
+ * Fetches the list of calls from the call log.
+ * <p>
+ * It will asynchronously update the content of the list view when the fetch completes.
+ */
+ public void fetchCalls() {
+ cancelFetch();
+ invalidate();
+ fetchNewCalls();
+ fetchOldCalls();
+ }
+
+ /** Fetches the list of new calls in the call log. */
+ private void fetchNewCalls() {
+ fetchCalls(QUERY_NEW_CALLS_TOKEN, true);
+ }
+
+ /** Fetch the list of old calls in the call log. */
+ private void fetchOldCalls() {
+ fetchCalls(QUERY_OLD_CALLS_TOKEN, false);
+ }
+
+ /** Fetches the list of calls in the call log, either the new one or the old ones. */
+ private void fetchCalls(int token, boolean isNew) {
+ String selection =
+ String.format("%s = 1 AND (%s = ? OR %s = ?)",
+ Calls.NEW, Calls.TYPE, Calls.TYPE);
+ String[] selectionArgs = new String[]{
+ Integer.toString(Calls.MISSED_TYPE),
+ Integer.toString(Calls.VOICEMAIL_TYPE),
+ };
+ if (!isNew) {
+ selection = String.format("NOT (%s)", selection);
+ }
+ startQuery(token, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
+ CallLogQuery._PROJECTION, selection, selectionArgs, Calls.DEFAULT_SORT_ORDER);
+ }
+
+ /** Cancel any pending fetch request. */
+ private void cancelFetch() {
+ cancelOperation(QUERY_NEW_CALLS_TOKEN);
+ cancelOperation(QUERY_OLD_CALLS_TOKEN);
+ }
+
+ /** Updates the missed calls to mark them as old. */
+ public void updateMissedCalls() {
+ // Mark all "new" missed calls as not new anymore
+ StringBuilder where = new StringBuilder();
+ where.append("type = ");
+ where.append(Calls.MISSED_TYPE);
+ where.append(" AND ");
+ where.append(Calls.NEW);
+ where.append(" = 1");
+
+ ContentValues values = new ContentValues(1);
+ values.put(Calls.NEW, "0");
+
+ startUpdate(UPDATE_MISSED_CALLS_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
+ values, where.toString(), null);
+ }
+
+ /**
+ * Invalidate the current list of calls.
+ * <p>
+ * This method is synchronized because it must close the cursors and reset them atomically.
+ */
+ private synchronized void invalidate() {
+ MoreCloseables.closeQuietly(mNewCallsCursor);
+ MoreCloseables.closeQuietly(mOldCallsCursor);
+ mNewCallsCursor = null;
+ mOldCallsCursor = null;
+ }
+
@Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ protected synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ if (token == QUERY_NEW_CALLS_TOKEN) {
+ // Store the returned cursor.
+ mNewCallsCursor = new ExtendedCursor(
+ cursor, CallLogQuery.SECTION_NAME, CallLogQuery.SECTION_NEW_ITEM);
+ } else if (token == QUERY_OLD_CALLS_TOKEN) {
+ // Store the returned cursor.
+ mOldCallsCursor = new ExtendedCursor(
+ cursor, CallLogQuery.SECTION_NAME, CallLogQuery.SECTION_OLD_ITEM);
+ } else {
+ Log.w(TAG, "Unknown query completed: ignoring: " + token);
+ return;
+ }
+
+ if (mNewCallsCursor != null && mOldCallsCursor != null) {
+ updateAdapterData(createMergedCursor());
+ }
+ }
+
+ /** Creates the merged cursor representing the data to show in the call log. */
+ @GuardedBy("this")
+ private Cursor createMergedCursor() {
+ try {
+ final boolean noNewCalls = mNewCallsCursor.getCount() == 0;
+ final boolean noOldCalls = mOldCallsCursor.getCount() == 0;
+
+ if (noNewCalls && noOldCalls) {
+ // Nothing in either cursors.
+ MoreCloseables.closeQuietly(mNewCallsCursor);
+ return mOldCallsCursor;
+ }
+
+ if (noNewCalls) {
+ // Return only the old calls.
+ MoreCloseables.closeQuietly(mNewCallsCursor);
+ return new MergeCursor(
+ new Cursor[]{ createOldCallsHeaderCursor(), mOldCallsCursor });
+ }
+
+ if (noOldCalls) {
+ // Return only the new calls.
+ MoreCloseables.closeQuietly(mOldCallsCursor);
+ return new MergeCursor(
+ new Cursor[]{ createNewCallsHeaderCursor(), mNewCallsCursor });
+ }
+
+ return new MergeCursor(new Cursor[]{
+ createNewCallsHeaderCursor(), mNewCallsCursor,
+ createOldCallsHeaderCursor(), mOldCallsCursor});
+ } finally {
+ // Any cursor still open is now owned, directly or indirectly, by the caller.
+ mNewCallsCursor = null;
+ mOldCallsCursor = null;
+ }
+ }
+
+ /**
+ * Updates the adapter in the call log fragment to show the new cursor data.
+ */
+ private void updateAdapterData(Cursor combinedCursor) {
final CallLogFragment fragment = mFragment.get();
if (fragment != null && fragment.getActivity() != null &&
!fragment.getActivity().isFinishing()) {
- final CallLogFragment.CallLogAdapter callsAdapter = fragment.mAdapter;
+ Log.d(TAG, "updating adapter");
+ final CallLogAdapter callsAdapter = fragment.mAdapter;
callsAdapter.setLoading(false);
- callsAdapter.changeCursor(cursor);
+ callsAdapter.changeCursor(combinedCursor);
if (fragment.mScrollToTop) {
final ListView listView = fragment.getListView();
if (listView.getFirstVisiblePosition() > 5) {
@@ -804,8 +1034,6 @@
listView.smoothScrollToPosition(0);
fragment.mScrollToTop = false;
}
- } else {
- cursor.close();
}
}
}
@@ -898,24 +1126,12 @@
}
private void resetNewCallsFlag() {
- // Mark all "new" missed calls as not new anymore
- StringBuilder where = new StringBuilder("type=");
- where.append(Calls.MISSED_TYPE);
- where.append(" AND new=1");
-
- ContentValues values = new ContentValues(1);
- values.put(Calls.NEW, "0");
- mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
- values, where.toString(), null);
+ mQueryHandler.updateMissedCalls();
}
private void startQuery() {
mAdapter.setLoading(true);
-
- // Cancel any pending queries
- mQueryHandler.cancelOperation(QUERY_TOKEN);
- mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
- CallLogQuery._PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
+ mQueryHandler.fetchCalls();
}
@Override
diff --git a/src/com/android/contacts/calllog/CallLogListItemHelper.java b/src/com/android/contacts/calllog/CallLogListItemHelper.java
index a8894da..4011929 100644
--- a/src/com/android/contacts/calllog/CallLogListItemHelper.java
+++ b/src/com/android/contacts/calllog/CallLogListItemHelper.java
@@ -57,10 +57,12 @@
* @param views the views to populate
* @param details the details of a phone call needed to fill in the data
* @param useIcons whether to use icons to show the type of the call
+ * @param isHighlighted whether to use the highlight text for the call
*/
public void setPhoneCallDetails(CallLogListItemViews views, PhoneCallDetails details,
- boolean useIcons) {
- mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details, useIcons);
+ boolean useIcons, boolean isHighlighted) {
+ mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details, useIcons,
+ isHighlighted);
if (views.callView != null) {
// The type of icon, call or play, is determined by the first call in the group.
views.callView.setImageDrawable(
diff --git a/src/com/android/contacts/calllog/CallLogListItemViews.java b/src/com/android/contacts/calllog/CallLogListItemViews.java
index 0cf15cc..75c54a4 100644
--- a/src/com/android/contacts/calllog/CallLogListItemViews.java
+++ b/src/com/android/contacts/calllog/CallLogListItemViews.java
@@ -22,6 +22,7 @@
import android.view.View;
import android.widget.ImageView;
import android.widget.QuickContactBadge;
+import android.widget.TextView;
/**
* Simple value object containing the various views within a call log entry.
@@ -33,22 +34,37 @@
public final ImageView callView;
/** The details of the phone call. */
public final PhoneCallDetailsViews phoneCallDetailsViews;
+ /** The item view for a stand-alone row, or null for other types of rows. */
+ public final View standAloneItemView;
+ /** The header view for a stand-alone row, or null for other types of rows. */
+ public final View standAloneHeaderView;
+ /** The text of the header in a stand-alone row, or null for other types of rows. */
+ public final TextView standAloneHeaderTextView;
private CallLogListItemViews(QuickContactBadge photoView, ImageView callView,
- PhoneCallDetailsViews phoneCallDetailsViews) {
+ PhoneCallDetailsViews phoneCallDetailsViews, View standAloneItemView,
+ View standAloneHeaderView, TextView standAloneHeaderTextView) {
this.photoView = photoView;
this.callView = callView;
this.phoneCallDetailsViews = phoneCallDetailsViews;
+ this.standAloneItemView = standAloneItemView;
+ this.standAloneHeaderView = standAloneHeaderView;
+ this.standAloneHeaderTextView = standAloneHeaderTextView;
}
public static CallLogListItemViews fromView(View view) {
return new CallLogListItemViews((QuickContactBadge) view.findViewById(R.id.contact_photo),
(ImageView) view.findViewById(R.id.call_icon),
- PhoneCallDetailsViews.fromView(view));
+ PhoneCallDetailsViews.fromView(view),
+ view.findViewById(R.id.call_log_item),
+ view.findViewById(R.id.call_log_header),
+ (TextView) view.findViewById(R.id.call_log_header_text));
}
public static CallLogListItemViews createForTest(QuickContactBadge photoView,
- ImageView callView, PhoneCallDetailsViews phoneCallDetailsViews) {
- return new CallLogListItemViews(photoView, callView, phoneCallDetailsViews);
+ ImageView callView, PhoneCallDetailsViews phoneCallDetailsViews,
+ View standAloneItemView, View standAloneHeaderView, TextView standAloneHeaderTextView) {
+ return new CallLogListItemViews(photoView, callView, phoneCallDetailsViews,
+ standAloneItemView, standAloneHeaderView, standAloneHeaderTextView);
}
}
diff --git a/src/com/android/contacts/calllog/CallTypeHelper.java b/src/com/android/contacts/calllog/CallTypeHelper.java
index b06a1c1..0c2068e 100644
--- a/src/com/android/contacts/calllog/CallTypeHelper.java
+++ b/src/com/android/contacts/calllog/CallTypeHelper.java
@@ -19,8 +19,13 @@
import com.android.contacts.R;
import android.content.res.Resources;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.provider.CallLog.Calls;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
/**
* Helper class to perform operations related to call types.
@@ -35,13 +40,17 @@
/** Icon for voicemails. */
private final Drawable mVoicemailDrawable;
/** Name used to identify incoming calls. */
- private final String mIncomingName;
+ private final CharSequence mIncomingName;
/** Name used to identify outgoing calls. */
- private final String mOutgoingName;
+ private final CharSequence mOutgoingName;
/** Name used to identify missed calls. */
- private final String mMissedName;
+ private final CharSequence mMissedName;
/** Name used to identify voicemail calls. */
- private final String mVoicemailName;
+ private final CharSequence mVoicemailName;
+ /** Name used to identify new missed calls. */
+ private final CharSequence mNewMissedName;
+ /** Name used to identify new voicemail calls. */
+ private final CharSequence mNewVoicemailName;
public CallTypeHelper(Resources resources, Drawable incomingDrawable, Drawable outgoingDrawable,
Drawable missedDrawable, Drawable voicemailDrawable) {
@@ -54,10 +63,14 @@
mOutgoingName = resources.getString(R.string.type_outgoing);
mMissedName = resources.getString(R.string.type_missed);
mVoicemailName = resources.getString(R.string.type_voicemail);
+ mNewMissedName = addBoldAndColor(mMissedName,
+ resources.getColor(R.color.call_log_missed_call_highlight_color));
+ mNewVoicemailName = addBoldAndColor(mVoicemailName,
+ resources.getColor(R.color.call_log_voicemail_highlight_color));
}
/** Returns the text used to represent the given call type. */
- public String getCallTypeText(int callType) {
+ public CharSequence getCallTypeText(int callType) {
switch (callType) {
case Calls.INCOMING_TYPE:
return mIncomingName;
@@ -76,6 +89,28 @@
}
}
+ /** Returns the text used to represent the given call type. */
+ public CharSequence getHighlightedCallTypeText(int callType) {
+ switch (callType) {
+ case Calls.INCOMING_TYPE:
+ // New incoming calls are not highlighted.
+ return mIncomingName;
+
+ case Calls.OUTGOING_TYPE:
+ // New outgoing calls are not highlighted.
+ return mOutgoingName;
+
+ case Calls.MISSED_TYPE:
+ return mNewMissedName;
+
+ case Calls.VOICEMAIL_TYPE:
+ return mNewVoicemailName;
+
+ default:
+ throw new IllegalArgumentException("invalid call type: " + callType);
+ }
+ }
+
/** Returns the drawable of the icon associated with the given call type. */
public Drawable getCallTypeDrawable(int callType) {
switch (callType) {
@@ -95,4 +130,13 @@
throw new IllegalArgumentException("invalid call type: " + callType);
}
}
+
+ /** Creates a SpannableString for the given text which is bold and in the given color. */
+ private CharSequence addBoldAndColor(CharSequence text, int color) {
+ int flags = Spanned.SPAN_INCLUSIVE_INCLUSIVE;
+ SpannableString result = new SpannableString(text);
+ result.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), flags);
+ result.setSpan(new ForegroundColorSpan(color), 0, text.length(), flags);
+ return result;
+ }
}
diff --git a/src/com/android/contacts/calllog/ExtendedCursor.java b/src/com/android/contacts/calllog/ExtendedCursor.java
new file mode 100644
index 0000000..b17c018
--- /dev/null
+++ b/src/com/android/contacts/calllog/ExtendedCursor.java
@@ -0,0 +1,132 @@
+/*
+ * 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.contacts.calllog;
+
+import com.android.common.io.MoreCloseables;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+
+/**
+ * Wraps a cursor to add an additional column with the same value for all rows.
+ * <p>
+ * The number of rows in the cursor and the set of columns is determined by the cursor being
+ * wrapped.
+ */
+public class ExtendedCursor extends AbstractCursor {
+ /** The cursor to wrap. */
+ private final Cursor mCursor;
+ /** The name of the additional column. */
+ private final String mColumnName;
+ /** The value to be assigned to the additional column. */
+ private final Object mValue;
+
+ /**
+ * Creates a new cursor which extends the given cursor by adding a column with a constant value.
+ *
+ * @param cursor the cursor to extend
+ * @param columnName the name of the additional column
+ * @param value the value to be assigned to the additional column
+ */
+ public ExtendedCursor(Cursor cursor, String columnName, Object value) {
+ mCursor = cursor;
+ mColumnName = columnName;
+ mValue = value;
+ }
+
+ @Override
+ public int getCount() {
+ return mCursor.getCount();
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ String[] columnNames = mCursor.getColumnNames();
+ int length = columnNames.length;
+ String[] extendedColumnNames = new String[length + 1];
+ System.arraycopy(columnNames, 0, extendedColumnNames, 0, length);
+ extendedColumnNames[length] = mColumnName;
+ return extendedColumnNames;
+ }
+
+ @Override
+ public String getString(int column) {
+ if (column == mCursor.getColumnCount()) {
+ return (String) mValue;
+ }
+ return mCursor.getString(column);
+ }
+
+ @Override
+ public short getShort(int column) {
+ if (column == mCursor.getColumnCount()) {
+ return (Short) mValue;
+ }
+ return mCursor.getShort(column);
+ }
+
+ @Override
+ public int getInt(int column) {
+ if (column == mCursor.getColumnCount()) {
+ return (Integer) mValue;
+ }
+ return mCursor.getInt(column);
+ }
+
+ @Override
+ public long getLong(int column) {
+ if (column == mCursor.getColumnCount()) {
+ return (Long) mValue;
+ }
+ return mCursor.getLong(column);
+ }
+
+ @Override
+ public float getFloat(int column) {
+ if (column == mCursor.getColumnCount()) {
+ return (Float) mValue;
+ }
+ return mCursor.getFloat(column);
+ }
+
+ @Override
+ public double getDouble(int column) {
+ if (column == mCursor.getColumnCount()) {
+ return (Double) mValue;
+ }
+ return mCursor.getDouble(column);
+ }
+
+ @Override
+ public boolean isNull(int column) {
+ if (column == mCursor.getColumnCount()) {
+ return mValue == null;
+ }
+ return mCursor.isNull(column);
+ }
+
+ @Override
+ public boolean onMove(int oldPosition, int newPosition) {
+ return mCursor.moveToPosition(newPosition);
+ }
+
+ @Override
+ public void close() {
+ MoreCloseables.closeQuietly(mCursor);
+ super.close();
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
index 4f95563..b628a5e 100644
--- a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
+++ b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
@@ -213,7 +213,7 @@
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(number, formattedNumber, new int[]{ Calls.INCOMING_TYPE },
TEST_DATE, TEST_DURATION),
- false);
+ false, false);
}
/** Sets the phone call details with default values and the given date. */
@@ -221,7 +221,7 @@
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER,
new int[]{ Calls.INCOMING_TYPE }, date, TEST_DURATION),
- false);
+ false, false);
}
/** Sets the phone call details with default values and the given call types using icons. */
@@ -238,6 +238,6 @@
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, callTypes, TEST_DATE,
TEST_DURATION),
- useIcons);
+ useIcons, false);
}
}
diff --git a/tests/src/com/android/contacts/activities/CallLogActivityTests.java b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
index 4004227..dfb6d0c 100644
--- a/tests/src/com/android/contacts/activities/CallLogActivityTests.java
+++ b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
@@ -55,13 +55,14 @@
extends ActivityInstrumentationTestCase2<CallLogActivity> {
private static final String TAG = "CallLogActivityTests";
- private static final String[] CALL_LOG_PROJECTION = new String[] {
+ private static final String[] EXTENDED_CALL_LOG_PROJECTION = new String[] {
Calls._ID,
Calls.NUMBER,
Calls.DATE,
Calls.DURATION,
Calls.TYPE,
Calls.COUNTRY_ISO,
+ CallLogFragment.CallLogQuery.SECTION_NAME,
};
private static final int RAND_DURATION = -1;
private static final long NOW = -1L;
@@ -123,7 +124,7 @@
mAdapter.disableRequestProcessingForTest();
mAdapter.stopRequestProcessing();
mParentView = new FrameLayout(mActivity);
- mCursor = new MatrixCursor(CALL_LOG_PROJECTION);
+ mCursor = new MatrixCursor(EXTENDED_CALL_LOG_PROJECTION);
buildIconMap();
}
@@ -431,6 +432,7 @@
}
row.add(type); // type
row.add(TEST_COUNTRY_ISO); // country ISO
+ row.add(CallLogFragment.CallLogQuery.SECTION_OLD_ITEM); // section
}
/**
diff --git a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
index b311454..ae8906f 100644
--- a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
@@ -87,7 +87,8 @@
mViews = CallLogListItemViews.createForTest(new QuickContactBadge(context),
new ImageView(context), PhoneCallDetailsViews.createForTest(new TextView(context),
new LinearLayout(context), new TextView(context), new TextView(context),
- new TextView(context), new TextView(context)));
+ new TextView(context), new TextView(context)),
+ new View(context), new View(context), new TextView(context));
}
@Override
@@ -135,7 +136,7 @@
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(number, formattedNumber, new int[]{ Calls.INCOMING_TYPE },
TEST_DATE, TEST_DURATION),
- true);
+ true, false);
}
/** Sets the details of a phone call using the specified call type. */
@@ -143,6 +144,6 @@
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(
TEST_NUMBER, TEST_FORMATTED_NUMBER, types, TEST_DATE, TEST_DURATION),
- true);
+ true, false);
}
}