Merge "Provide a simple button to add a contact to the default group "My Contacts""
diff --git a/res/drawable-hdpi/ic_call_incoming_holo_dark.png b/res/drawable-hdpi/ic_call_incoming_holo_dark.png
index df6f190..8351f48 100644
--- a/res/drawable-hdpi/ic_call_incoming_holo_dark.png
+++ b/res/drawable-hdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_missed_holo_dark.png b/res/drawable-hdpi/ic_call_missed_holo_dark.png
index a6bdb8d..7c17c30 100644
--- a/res/drawable-hdpi/ic_call_missed_holo_dark.png
+++ b/res/drawable-hdpi/ic_call_missed_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_outgoing_holo_dark.png b/res/drawable-hdpi/ic_call_outgoing_holo_dark.png
index 657655d..3b9e0f8 100644
--- a/res/drawable-hdpi/ic_call_outgoing_holo_dark.png
+++ b/res/drawable-hdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_voicemail_holo_dark.png b/res/drawable-hdpi/ic_call_voicemail_holo_dark.png
index 734050d..6d64a36 100644
--- a/res/drawable-hdpi/ic_call_voicemail_holo_dark.png
+++ b/res/drawable-hdpi/ic_call_voicemail_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_incoming_holo_dark.png b/res/drawable-mdpi/ic_call_incoming_holo_dark.png
index 055f9c6..8dcb350 100644
--- a/res/drawable-mdpi/ic_call_incoming_holo_dark.png
+++ b/res/drawable-mdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_missed_holo_dark.png b/res/drawable-mdpi/ic_call_missed_holo_dark.png
index ab098ee..af030cf 100644
--- a/res/drawable-mdpi/ic_call_missed_holo_dark.png
+++ b/res/drawable-mdpi/ic_call_missed_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_outgoing_holo_dark.png b/res/drawable-mdpi/ic_call_outgoing_holo_dark.png
index aaf2b87..38a01b7 100644
--- a/res/drawable-mdpi/ic_call_outgoing_holo_dark.png
+++ b/res/drawable-mdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_voicemail_holo_dark.png b/res/drawable-mdpi/ic_call_voicemail_holo_dark.png
index bb566bb..bf6d006 100644
--- a/res/drawable-mdpi/ic_call_voicemail_holo_dark.png
+++ b/res/drawable-mdpi/ic_call_voicemail_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_incoming_holo_dark.png b/res/drawable-xhdpi/ic_call_incoming_holo_dark.png
index 3671ff0..8eb5f3d 100644
--- a/res/drawable-xhdpi/ic_call_incoming_holo_dark.png
+++ b/res/drawable-xhdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_missed_holo_dark.png b/res/drawable-xhdpi/ic_call_missed_holo_dark.png
index 66129f2..6d09a4e 100644
--- a/res/drawable-xhdpi/ic_call_missed_holo_dark.png
+++ b/res/drawable-xhdpi/ic_call_missed_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png b/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png
index 4b48928..6360504 100644
--- a/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png
+++ b/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png b/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png
index 97443fb..d9684d1 100644
--- a/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png
+++ b/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png
Binary files differ
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 7006691..806b33f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1613,4 +1613,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 af36e21..4638b2e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -16,6 +16,7 @@
<resources>
<style name="DialtactsTheme" parent="android:Theme.Holo.SplitActionBarWhenNarrow">
<item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowBackground">@android:color/black</item>
<item name="activated_background">@drawable/list_item_activated_background</item>
<item name="section_header_background">@drawable/list_title_holo</item>
<item name="list_section_header_height">32dip</item>
@@ -40,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>
@@ -154,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/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 194d0ba..775009e 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -371,19 +371,29 @@
}
/**
- * Creates an intent that can be sent to this service to create a new group.
+ * Creates an intent that can be sent to this service to create a new group as
+ * well as add new members at the same time.
+ *
+ * @param context of the application
+ * @param account in which the group should be created
+ * @param label is the name of the group (cannot be null)
+ * @param rawContactsToAdd is an array of raw contact IDs for contacts that
+ * should be added to the group
+ * @param callbackActivity is the activity to send the callback intent to
+ * @param callbackAction is the intent action for the callback intent
*/
- public static Intent createNewGroupIntent(Context context, Account account, String label,
- Class<?> callbackActivity, String callbackAction) {
+ public static Intent createNewGroupIntent(Context context, Account account,
+ String label, long[] rawContactsToAdd, Class<?> callbackActivity,
+ String callbackAction) {
Intent serviceIntent = new Intent(context, ContactSaveService.class);
serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
// Callback intent will be invoked by the service once the new group is
- // created. The service will put a group membership row in the extras
- // of the callback intent.
+ // created.
Intent callbackIntent = new Intent(context, callbackActivity);
callbackIntent.setAction(callbackAction);
serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
@@ -392,28 +402,41 @@
}
private void createGroup(Intent intent) {
- String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
- String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
- String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
+ final String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
+ final String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
+ final String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
+ final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
ContentValues values = new ContentValues();
values.put(Groups.ACCOUNT_TYPE, accountType);
values.put(Groups.ACCOUNT_NAME, accountName);
values.put(Groups.TITLE, label);
- Uri groupUri = getContentResolver().insert(Groups.CONTENT_URI, values);
+ final ContentResolver resolver = getContentResolver();
+
+ // Create the new group
+ final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
+
+ // If there's no URI, then the insertion failed. Abort early because group members can't be
+ // added if the group doesn't exist
if (groupUri == null) {
+ Log.e(TAG, "Couldn't create group with label " + label);
return;
}
+ // Add new group members
+ addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
+
+ // TODO: Move this into the contact editor where it belongs. This needs to be integrated
+ // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
values.clear();
values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
callbackIntent.setData(groupUri);
+ // TODO: This can be taken out when the above TODO is addressed
callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
-
deliverCallback(callbackIntent);
}
@@ -527,10 +550,23 @@
if (label != null) {
ContentValues values = new ContentValues();
values.put(Groups.TITLE, label);
- getContentResolver().update(groupUri, values, null, null);
+ resolver.update(groupUri, values, null, null);
}
- // Add new group members
+ // Add and remove members if necessary
+ addMembersToGroup(resolver, rawContactsToAdd, groupId);
+ removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
+
+ Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
+ callbackIntent.setData(groupUri);
+ deliverCallback(callbackIntent);
+ }
+
+ private void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
+ long groupId) {
+ if (rawContactsToAdd == null) {
+ return;
+ }
for (long rawContactId : rawContactsToAdd) {
try {
final ArrayList<ContentProviderOperation> rawContactOperations =
@@ -577,8 +613,13 @@
String.valueOf(groupId), e);
}
}
+ }
- // Remove group members
+ private void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
+ long groupId) {
+ if (rawContactsToRemove == null) {
+ return;
+ }
for (long rawContactId : rawContactsToRemove) {
// Apply the delete operation on the data row for the given raw contact's
// membership in the given group. If no contact matches the provided selection, then
@@ -588,10 +629,6 @@
new String[] { String.valueOf(rawContactId),
GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
}
-
- Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
- callbackIntent.setData(groupUri);
- deliverCallback(callbackIntent);
}
/**
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/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 7456967..0637fd5 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -174,11 +174,7 @@
@Override
public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
boolean globalSearch) {
- if (globalSearch) {
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
- } else {
- ContactsSearchManager.startSearch(this, initialQuery);
- }
+ // Ignore search key press
}
@Override
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index cc0be9d..1ee7f1d 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -122,11 +122,7 @@
@Override
public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
boolean globalSearch) {
- if (globalSearch) {
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
- } else {
- ContactsSearchManager.startSearch(this, initialQuery);
- }
+ // Ignore search key press
}
@Override
diff --git a/src/com/android/contacts/activities/GroupDetailActivity.java b/src/com/android/contacts/activities/GroupDetailActivity.java
index 7a74bfd..635c130 100644
--- a/src/com/android/contacts/activities/GroupDetailActivity.java
+++ b/src/com/android/contacts/activities/GroupDetailActivity.java
@@ -90,4 +90,10 @@
}
return super.onOptionsItemSelected(item);
}
+
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+ boolean globalSearch) {
+ // Ignore search key press
+ }
}
diff --git a/src/com/android/contacts/activities/GroupEditorActivity.java b/src/com/android/contacts/activities/GroupEditorActivity.java
index fa41051..ecadcec 100644
--- a/src/com/android/contacts/activities/GroupEditorActivity.java
+++ b/src/com/android/contacts/activities/GroupEditorActivity.java
@@ -85,11 +85,7 @@
@Override
public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
boolean globalSearch) {
- if (globalSearch) {
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
- } else {
- ContactsSearchManager.startSearch(this, initialQuery);
- }
+ // Ignore search key press
}
@Override
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 90e405a..7e6828e 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,11 +146,8 @@
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 CallLogQueryHandler mCallLogQueryHandler;
private String mVoiceMailNumber;
private String mCurrentCountryIso;
private boolean mScrollToTop;
@@ -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 @@
}
}
- private static final class QueryHandler extends AsyncQueryHandler {
+ /** Handles asynchronous queries to the call log. */
+ private static final class CallLogQueryHandler 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.
@@ -783,29 +854,174 @@
return new CatchingWorkerHandler(looper);
}
- public QueryHandler(CallLogFragment fragment) {
+ public CallLogQueryHandler(CallLogFragment fragment) {
super(fragment.getActivity().getContentResolver());
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) {
- final CallLogFragment fragment = mFragment.get();
- if (fragment != null && fragment.getActivity() != null &&
- !fragment.getActivity().isFinishing()) {
- final CallLogFragment.CallLogAdapter callsAdapter = fragment.mAdapter;
- callsAdapter.setLoading(false);
- callsAdapter.changeCursor(cursor);
- if (fragment.mScrollToTop) {
- final ListView listView = fragment.getListView();
- if (listView.getFirstVisiblePosition() > 5) {
- listView.setSelection(5);
- }
- listView.smoothScrollToPosition(0);
- fragment.mScrollToTop = false;
- }
+ 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 {
- cursor.close();
+ 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.onCallsFetched(combinedCursor);
}
}
}
@@ -816,13 +1032,31 @@
mVoiceMailNumber = ((TelephonyManager) getActivity().getSystemService(
Context.TELEPHONY_SERVICE)).getVoiceMailNumber();
- mQueryHandler = new QueryHandler(this);
+ mCallLogQueryHandler = new CallLogQueryHandler(this);
mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
setHasOptionsMenu(true);
}
+ /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
+ public void onCallsFetched(Cursor cursor) {
+ if (getActivity() == null || getActivity().isFinishing()) {
+ return;
+ }
+ Log.d(TAG, "updating adapter");
+ mAdapter.setLoading(false);
+ mAdapter.changeCursor(cursor);
+ if (mScrollToTop) {
+ final ListView listView = getListView();
+ if (listView.getFirstVisiblePosition() > 5) {
+ listView.setSelection(5);
+ }
+ listView.smoothScrollToPosition(0);
+ mScrollToTop = false;
+ }
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
return inflater.inflate(R.layout.call_log_fragment, container, false);
@@ -898,24 +1132,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);
+ mCallLogQueryHandler.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);
+ mCallLogQueryHandler.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/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 87d4fd6..717db46 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -101,8 +101,6 @@
private View mDelete;
private ToneGenerator mToneGenerator;
private Object mToneGeneratorLock = new Object();
- private Drawable mDigitsBackground;
- private Drawable mDigitsEmptyBackground;
private View mDialpad;
private View mAdditionalButtonsRow;
@@ -216,14 +214,9 @@
mDigits.getText().clear();
}
- // Previously we changed background color depending on its text status. Now we don't.
- // TODO: remove the relevant codes entirely once we're sure we won't use them.
- /*if (!isDigitsEmpty()) {
- mDigits.setBackgroundDrawable(mDigitsBackground);
- } else {
+ if (isDigitsEmpty()) {
mDigits.setCursorVisible(false);
- mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
- }*/
+ }
updateDialAndDeleteButtonEnabledState();
}
@@ -250,8 +243,6 @@
// Load up the resources for the text field.
Resources r = getResources();
- mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
- mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
mDigits = (EditText) fragmentView.findViewById(R.id.digits);
mDigits.setKeyListener(DialerKeyListener.getInstance());
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index 76f48a3..da57c82 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -238,20 +238,11 @@
// Account specified in Intent
mAccountName = account.name;
mAccountType = account.type;
- setupAccountHeader();
+ setupEditorForAccount();
} else {
// No Account specified. Let the user choose from a disambiguation dialog.
selectAccountAndCreateGroup();
}
-
- mStatus = Status.EDITING;
-
- // The user wants to create a new group, temporarily hide the "add members" text view
- // TODO: Need to allow users to add members if it's a new group. Under the current
- // approach, we can't add members because it needs a group ID in order to save,
- // and we don't have a group ID for a new group until the whole group is saved.
- // Take this out when batch add/remove members is working.
- mAutoCompleteTextView.setVisibility(View.GONE);
} else {
throw new IllegalArgumentException("Unknown Action String " + mAction +
". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
@@ -278,7 +269,7 @@
if (accounts.size() == 1) {
mAccountName = accounts.get(0).name;
mAccountType = accounts.get(0).type;
- setupAccountHeader();
+ setupEditorForAccount();
return; // Don't show a dialog.
}
@@ -292,7 +283,7 @@
public void onAccountChosen(int requestCode, Account account) {
mAccountName = account.name;
mAccountType = account.type;
- setupAccountHeader();
+ setupEditorForAccount();
}
@Override
@@ -304,9 +295,10 @@
}
/**
- * Sets up the account header.
+ * Sets up the editor based on the group's account name and type.
*/
- private void setupAccountHeader() {
+ private void setupEditorForAccount() {
+ // Setup the account header
final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(mContext);
final AccountType accountType = accountTypeManager.getAccountType(mAccountType);
CharSequence accountTypeDisplayLabel = accountType.getDisplayLabel(mContext);
@@ -316,6 +308,31 @@
}
mAccountTypeTextView.setText(accountTypeDisplayLabel);
mAccountIcon.setImageDrawable(accountType.getDisplayIcon(mContext));
+
+ // Setup the autocomplete adapter (for contacts to suggest to add to the group) based on the
+ // account name and type
+ mAutoCompleteAdapter = new SuggestedMemberListAdapter(mContext,
+ android.R.layout.simple_dropdown_item_1line);
+ mAutoCompleteAdapter.setContentResolver(mContentResolver);
+ mAutoCompleteAdapter.setAccountType(mAccountType);
+ mAutoCompleteAdapter.setAccountName(mAccountName);
+ mAutoCompleteTextView.setAdapter(mAutoCompleteAdapter);
+ mAutoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ SuggestedMember member = mAutoCompleteAdapter.getItem(position);
+ loadMemberToAddToGroup(member.getRawContactId(),
+ String.valueOf(member.getContactId()));
+
+ // Update the autocomplete adapter so the contact doesn't get suggested again
+ mAutoCompleteAdapter.addNewMember(member.getContactId());
+
+ // Clear out the text field
+ mAutoCompleteTextView.setText("");
+ }
+ });
+
+ mStatus = Status.EDITING;
}
public void load(String action, Uri groupUri, Bundle intentExtras) {
@@ -349,7 +366,7 @@
// focus on the field).
mGroupNameView.setText(mOriginalGroupName);
mGroupNameView.setFocusable(!mGroupNameIsReadOnly);
- setupAccountHeader();
+ setupEditorForAccount();
}
public void loadMemberToAddToGroup(long rawContactId, String contactId) {
@@ -380,8 +397,7 @@
}
private boolean revert() {
- if (mGroupNameView.getText() != null &&
- mGroupNameView.getText().toString().equals(mOriginalGroupName)) {
+ if (!hasNameChange() && !hasMembershipChange()) {
doRevertAction();
} else {
CancelEditDialogFragment.show(this);
@@ -453,24 +469,20 @@
}
Intent saveIntent = null;
if (mAction == Intent.ACTION_INSERT) {
- // TODO: Perform similar work to add members at the same time as creating a new group
+ // Create array of raw contact IDs for contacts to add to the group
+ long[] membersToAddArray = convertToArray(mListMembersToAdd);
+
+ // Create the save intent to create the group and add members at the same time
saveIntent = ContactSaveService.createNewGroupIntent(activity,
new Account(mAccountName, mAccountType), mGroupNameView.getText().toString(),
- activity.getClass(), GroupEditorActivity.ACTION_SAVE_COMPLETED);
+ membersToAddArray, activity.getClass(),
+ GroupEditorActivity.ACTION_SAVE_COMPLETED);
} else if (mAction == Intent.ACTION_EDIT) {
// Create array of raw contact IDs for contacts to add to the group
- int membersToAddCount = mListMembersToAdd.size();
- long[] membersToAddArray = new long[membersToAddCount];
- for (int i = 0; i < membersToAddCount; i++) {
- membersToAddArray[i] = mListMembersToAdd.get(i).getRawContactId();
- }
+ long[] membersToAddArray = convertToArray(mListMembersToAdd);
// Create array of raw contact IDs for contacts to add to the group
- int membersToRemoveCount = mListMembersToRemove.size();
- long[] membersToRemoveArray = new long[membersToRemoveCount];
- for (int i = 0; i < membersToRemoveCount; i++) {
- membersToRemoveArray[i] = mListMembersToRemove.get(i).getRawContactId();
- }
+ long[] membersToRemoveArray = convertToArray(mListMembersToRemove);
// Create the update intent (which includes the updated group name if necessary)
saveIntent = ContactSaveService.createGroupUpdateIntent(activity, mGroupId,
@@ -555,6 +567,15 @@
return groupNameFromTextView;
}
+ private static long[] convertToArray(List<Member> listMembers) {
+ int size = listMembers.size();
+ long[] membersArray = new long[size];
+ for (int i = 0; i < size; i++) {
+ membersArray[i] = listMembers.get(i).getRawContactId();
+ }
+ return membersArray;
+ }
+
private void addExistingMembers(List<Member> members, List<Long> listContactIds) {
mListToDisplay.addAll(members);
mMemberListAdapter.notifyDataSetChanged();
@@ -605,7 +626,6 @@
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- mStatus = Status.EDITING;
bindGroupMetaData(data);
// Load existing members
@@ -651,27 +671,6 @@
data.close();
}
- mAutoCompleteAdapter = new SuggestedMemberListAdapter(getActivity(),
- android.R.layout.simple_dropdown_item_1line);
- mAutoCompleteAdapter.setContentResolver(mContentResolver);
- mAutoCompleteAdapter.setAccountType(mAccountType);
- mAutoCompleteAdapter.setAccountName(mAccountName);
- mAutoCompleteTextView.setAdapter(mAutoCompleteAdapter);
- mAutoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- SuggestedMember member = mAutoCompleteAdapter.getItem(position);
- loadMemberToAddToGroup(member.getRawContactId(),
- String.valueOf(member.getContactId()));
-
- // Update the autocomplete adapter so the contact doesn't get suggested again
- mAutoCompleteAdapter.addNewMember(member.getContactId());
-
- // Clear out the text field
- mAutoCompleteTextView.setText("");
- }
- });
-
// Update the display list
addExistingMembers(listExistingMembers, listContactIds);
diff --git a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
index 8991928..87d83b4 100644
--- a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
+++ b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
@@ -60,6 +60,7 @@
Activity activity = getActivity();
activity.startService(ContactSaveService.createNewGroupIntent(activity,
new Account(accountName, accountType), groupLabel,
+ null /* no new members to add */,
activity.getClass(), Intent.ACTION_EDIT));
}
}
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);
}
}