Merge "Pass data to ContactsPreferenceActivity"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c2c4eab..bbc1713 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.contacts"
- android:versionCode="10500"
- android:versionName="1.5.0">
+ android:versionCode="10501"
+ android:versionName="1.5.1">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="23" />
<original-package android:name="com.android.contacts" />
@@ -245,7 +245,7 @@
<!-- Displays the members of a group in a list -->
<activity android:name=".activities.GroupMembersActivity"
- android:theme="@style/ContactPickerTheme"/>
+ android:theme="@style/PeopleActivityTheme"/>
<!-- Views the details of a single group -->
<activity android:name=".activities.GroupDetailActivity"
diff --git a/res/layout-v23/edit_spinner.xml b/res/layout-v23/edit_spinner.xml
index 0c20ab9..37015f7 100644
--- a/res/layout-v23/edit_spinner.xml
+++ b/res/layout-v23/edit_spinner.xml
@@ -21,8 +21,7 @@
android:id="@+id/spinner"
android:layout_gravity="bottom|start"
style="@android:style/Widget.Material.Spinner.Underlined"
- android:dropDownWidth="@dimen/editor_type_label_dropdown_width"
- android:layout_width="@dimen/editor_type_label_width"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/editor_min_line_item_height"
android:paddingBottom="0dp"
android:paddingTop="0dp"
diff --git a/res/layout/edit_spinner.xml b/res/layout/edit_spinner.xml
index 9e6b465..b1c879c 100644
--- a/res/layout/edit_spinner.xml
+++ b/res/layout/edit_spinner.xml
@@ -21,8 +21,7 @@
android:id="@+id/spinner"
android:layout_gravity="bottom|start"
style="@android:style/Widget.Material.Spinner.Underlined"
- android:dropDownWidth="@dimen/editor_type_label_dropdown_width"
- android:layout_width="@dimen/editor_type_label_width"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/editor_min_line_item_height"
android:paddingBottom="0dp"
android:paddingTop="0dp"
diff --git a/res/layout/group_editor_autocomplete_view.xml b/res/layout/group_editor_autocomplete_view.xml
deleted file mode 100644
index c8e716a..0000000
--- a/res/layout/group_editor_autocomplete_view.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<!-- Autocomplete text input field for adding new members to a group in the group editor -->
-
-<AutoCompleteTextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorPrimary"
- android:imeOptions="flagNoExtractUi|flagNoFullscreen"
- android:hint="@string/enter_contact_name"
- android:minHeight="48dip"
- android:paddingLeft="@dimen/group_editor_autocomplete_left_padding"
- android:paddingStart="@dimen/group_editor_autocomplete_left_padding"/>
\ No newline at end of file
diff --git a/res/layout/group_editor_view.xml b/res/layout/group_editor_view.xml
index d94853d..72ceaf3 100644
--- a/res/layout/group_editor_view.xml
+++ b/res/layout/group_editor_view.xml
@@ -45,10 +45,6 @@
android:paddingStart="8dip"/>
<include
- layout="@layout/group_editor_autocomplete_view"
- android:id="@+id/add_member_field"/>
-
- <include
layout="@layout/group_editor_existing_member_list"
android:id="@android:id/list"/>
</LinearLayout>
diff --git a/res/layout/group_members_activity.xml b/res/layout/group_members_activity.xml
index e8cc594..eecec50 100644
--- a/res/layout/group_members_activity.xml
+++ b/res/layout/group_members_activity.xml
@@ -19,4 +19,16 @@
android:id="@+id/fragment_container"
android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
\ No newline at end of file
+ android:layout_height="match_parent">
+
+ <include
+ layout="@layout/people_activity_toolbar"
+ android:id="@+id/toolbar_parent" />
+
+ <com.android.contacts.widget.NoSwipeViewPager
+ android:id="@+id/view_pager"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/menu/edit_group.xml b/res/menu/edit_group.xml
index b94563f..c2d7f47 100644
--- a/res/menu/edit_group.xml
+++ b/res/menu/edit_group.xml
@@ -14,15 +14,22 @@
limitations under the License.
-->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:contacts="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_add"
+ android:icon="@drawable/ic_person_add_tinted_24dp"
+ android:title="@string/menu_addToGroup"
+ contacts:showAsAction="ifRoom" />
+
<item
android:id="@+id/menu_save"
- android:showAsAction="always"
android:icon="@drawable/ic_done_wht_24dp"
- android:title="@string/menu_save" />
+ android:title="@string/menu_save"
+ contacts:showAsAction="ifRoom" />
+
<item
android:id="@+id/menu_discard"
- android:alphabeticShortcut="q"
- android:title="@string/menu_discard"
- android:showAsAction="withText" />
+ android:title="@string/menu_discard" />
</menu>
diff --git a/res/menu/view_group.xml b/res/menu/view_group.xml
index 669f401..6c5979e 100644
--- a/res/menu/view_group.xml
+++ b/res/menu/view_group.xml
@@ -14,13 +14,20 @@
limitations under the License.
-->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:contacts="http://schemas.android.com/apk/res-auto">
+
<item
android:id="@+id/menu_edit_group"
+ android:icon="@drawable/ic_create_24dp"
android:title="@string/menu_editGroup"
- android:alphabeticShortcut="e" />
+ contacts:showAsAction="ifRoom" />
<item
android:id="@+id/menu_delete_group"
android:title="@string/menu_deleteGroup" />
+
+ <item
+ android:id="@+id/menu_remove_from_group"
+ android:title="@string/menu_removeFromGroup" />
</menu>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 78bec34..b519318 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -52,8 +52,6 @@
<dimen name="group_editor_member_list_right_margin">4dip</dimen>
<!-- Account title left padding -->
<dimen name="account_container_left_padding">16dip</dimen>
- <!-- Left padding of the auto complete field to line hint text up with member list -->
- <dimen name="group_editor_autocomplete_left_padding">16dip</dimen>
<dimen name="contact_detail_list_top_padding">8dip</dimen>
<dimen name="frequently_contacted_title_text_size">24sp</dimen>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f60670c..6d516cf 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -102,9 +102,6 @@
<!-- Width of the Type-Label in the Editor -->
<dimen name="editor_type_label_width">150dip</dimen>
- <!-- Width of the drop down that appears when you click on the Type-Label spinner in the editor -->
- <dimen name="editor_type_label_dropdown_width">150dp</dimen>
-
<!-- Left padding of the label in the add field button for the contact editor -->
<dimen name="editor_add_field_label_left_padding">16dip</dimen>
@@ -157,9 +154,6 @@
<!-- Right margin for the group member list to match the built in margin in the autocomplete asset -->
<dimen name="group_editor_member_list_right_margin">4dip</dimen>
- <!-- Left padding of the auto complete field to line hint text up with member list -->
- <dimen name="group_editor_autocomplete_left_padding">8dip</dimen>
-
<!-- Border padding for the group detail fragment -->
<dimen name="group_detail_border_padding">0dip</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 37e276f..22169e4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -135,10 +135,16 @@
<string name="menu_editGroup">Edit</string>
<!-- Menu item that deletes the currently selected label [CHAR LIMIT=30] -->
- <string name="menu_deleteGroup">Delete</string>
+ <string name="menu_deleteGroup">Delete label</string>
+
+ <!-- Menu item to search for contacts to add to the currently selected label. CHAR LIMIT=30] -->
+ <string name="menu_addToGroup">Add contact</string>
+
+ <!-- Menu item to remove the currently selected contacts from the currently selected label. [CHAR LIMIT=60] -->
+ <string name="menu_removeFromGroup">Remove from label</string>
<!-- Menu item (in the action bar) that creates a new contact [CHAR LIMIT=30] -->
- <string name="menu_new_contact_action_bar">Add Contact</string>
+ <string name="menu_new_contact_action_bar">Add contact</string>
<!-- Menu item (in the action bar) that creates a new label [CHAR LIMIT=30] -->
<string name="menu_new_group_action_bar">Create new…</string>
@@ -284,6 +290,12 @@
<!-- The text displayed when the labels list is empty while displaying all labels [CHAR LIMIT=30] -->
<string name="noGroups">No labels.</string>
+ <!-- The text displayed when the groups list is empty and no accounts are set on the device while displaying all groups [CHAR LIMIT=NONE] -->
+ <string name="noAccounts">To create groups you need an account.</string>
+
+ <!-- The text displayed to instruct users to add members to a group (when viewing a group detail page for a group with no members) [CHAR LIMIT=50] -->
+ <string name="addPeopleToGroup">To add some, edit the group.</string>
+
<!-- The text displayed when there are no members that have this label while displaying the label detail page [CHAR LIMIT=70] -->
<string name="emptyGroup">No people with this label.</string>
@@ -433,6 +445,9 @@
<!-- String describing which account a contact came from when editing it -->
<string name="from_account_format"><xliff:g id="source" example="user@gmail.com">%1$s</xliff:g></string>
+ <!-- Text used to explain that a group cannot be edited since the data is read only [CHAR LIMIT=40] -->
+ <string name="group_read_only">Not editable on this device.</string>
+
<!-- An option in the 'Contact photo' dialog, if there is no photo yet [CHAR LIMIT=50] -->
<string name="take_photo">Take photo</string>
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 5c9c899..08eb1c7 100755
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -45,6 +45,7 @@
import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
+import android.support.v4.os.ResultReceiver;
import android.util.Log;
import android.widget.Toast;
@@ -59,7 +60,6 @@
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.compat.PinnedPositionsCompat;
-import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor.SaveMode;
import com.android.contacts.util.ContactPhotoUtils;
import com.google.common.collect.Lists;
@@ -86,6 +86,8 @@
public static final String EXTRA_DATA_SET = "dataSet";
public static final String EXTRA_CONTENT_VALUES = "contentValues";
public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
+ public static final String EXTRA_RESULT_RECEIVER = "resultReceiver";
+ public static final String EXTRA_RAW_CONTACT_IDS = "rawContactIds";
public static final String ACTION_SAVE_CONTACT = "saveContact";
public static final String EXTRA_CONTACT_STATE = "state";
@@ -114,6 +116,8 @@
public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
public static final String EXTRA_DATA_ID = "dataId";
+ public static final String ACTION_SPLIT_CONTACT = "splitContact";
+
public static final String ACTION_JOIN_CONTACTS = "joinContacts";
public static final String ACTION_JOIN_SEVERAL_CONTACTS = "joinSeveralContacts";
public static final String EXTRA_CONTACT_ID1 = "contactId1";
@@ -125,6 +129,10 @@
public static final String ACTION_SET_RINGTONE = "setRingtone";
public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone";
+ public static final int CP2_ERROR = 0;
+ public static final int CONTACTS_LINKED = 1;
+ public static final int CONTACTS_SPLIT = 2;
+
private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
Data.MIMETYPE,
Data.IS_PRIMARY,
@@ -264,6 +272,8 @@
deleteMultipleContacts(intent);
} else if (ACTION_DELETE_CONTACT.equals(action)) {
deleteContact(intent);
+ } else if (ACTION_SPLIT_CONTACT.equals(action)) {
+ splitContact(intent);
} else if (ACTION_JOIN_CONTACTS.equals(action)) {
joinContacts(intent);
} else if (ACTION_JOIN_SEVERAL_CONTACTS.equals(action)) {
@@ -1113,6 +1123,85 @@
}
/**
+ * Creates an intent that can be sent to this service to split a contact into it's constituent
+ * pieces.
+ */
+ public static Intent createSplitContactIntent(Context context, long[][] rawContactIds,
+ ResultReceiver receiver) {
+ final Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_SPLIT_CONTACT);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACT_IDS, rawContactIds);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_RESULT_RECEIVER, receiver);
+ return serviceIntent;
+ }
+
+ private void splitContact(Intent intent) {
+ final long rawContactIds[][] = (long[][]) intent
+ .getSerializableExtra(EXTRA_RAW_CONTACT_IDS);
+ if (rawContactIds == null) {
+ Log.e(TAG, "Invalid argument for splitContact request");
+ return;
+ }
+ final int batchSize = MAX_CONTACTS_PROVIDER_BATCH_SIZE;
+ final ContentResolver resolver = getContentResolver();
+ final ArrayList<ContentProviderOperation> operations = new ArrayList<>(batchSize);
+ final ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
+ for (int i = 0; i < rawContactIds.length; i++) {
+ for (int j = 0; j < rawContactIds.length; j++) {
+ if (i != j) {
+ if (!buildSplitTwoContacts(operations, rawContactIds[i], rawContactIds[j])) {
+ if (receiver != null) {
+ receiver.send(CP2_ERROR, new Bundle());
+ return;
+ }
+ }
+ }
+ }
+ }
+ if (operations.size() > 0 && !applyOperations(resolver, operations)) {
+ if (receiver != null) {
+ receiver.send(CP2_ERROR, new Bundle());
+ }
+ return;
+ }
+ if (receiver != null) {
+ receiver.send(CONTACTS_SPLIT, new Bundle());
+ } else {
+ showToast(R.string.contactUnlinkedToast);
+ }
+ }
+
+ /**
+ * Adds insert aggregation exception ContentProviderOperations between {@param rawContactIds1}
+ * and {@param rawContactIds2} to {@param operations}.
+ * @return false if an error occurred, true otherwise.
+ */
+ private boolean buildSplitTwoContacts(ArrayList<ContentProviderOperation> operations,
+ long[] rawContactIds1, long[] rawContactIds2) {
+ if (rawContactIds1 == null || rawContactIds2 == null) {
+ Log.e(TAG, "Invalid arguments for splitContact request");
+ return false;
+ }
+ // For each pair of raw contacts, insert an aggregation exception
+ final ContentResolver resolver = getContentResolver();
+ // The maximum number of operations per batch (aka yield point) is 500. See b/22480225
+ final int batchSize = MAX_CONTACTS_PROVIDER_BATCH_SIZE;
+ for (int i = 0; i < rawContactIds1.length; i++) {
+ for (int j = 0; j < rawContactIds2.length; j++) {
+ buildSplitContactDiff(operations, rawContactIds1[i], rawContactIds2[j]);
+ // Before we get to 500 we need to flush the operations list
+ if (operations.size() > 0 && operations.size() % batchSize == 0) {
+ if (!applyOperations(resolver, operations)) {
+ return false;
+ }
+ operations.clear();
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
* Creates an intent that can be sent to this service to join two contacts.
* The resulting contact uses the name from {@param contactId1} if possible.
*/
@@ -1135,13 +1224,22 @@
* Creates an intent to join all raw contacts inside {@param contactIds}'s contacts.
* No special attention is paid to where the resulting contact's name is taken from.
*/
- public static Intent createJoinSeveralContactsIntent(Context context, long[] contactIds) {
- Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ public static Intent createJoinSeveralContactsIntent(Context context, long[] contactIds,
+ ResultReceiver receiver) {
+ final Intent serviceIntent = new Intent(context, ContactSaveService.class);
serviceIntent.setAction(ContactSaveService.ACTION_JOIN_SEVERAL_CONTACTS);
serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_RESULT_RECEIVER, receiver);
return serviceIntent;
}
+ /**
+ * Creates an intent to join all raw contacts inside {@param contactIds}'s contacts.
+ * No special attention is paid to where the resulting contact's name is taken from.
+ */
+ public static Intent createJoinSeveralContactsIntent(Context context, long[] contactIds) {
+ return createJoinSeveralContactsIntent(context, contactIds, /* receiver = */ null);
+ }
private interface JoinContactQuery {
String[] PROJECTION = {
@@ -1173,9 +1271,11 @@
private void joinSeveralContacts(Intent intent) {
final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS);
+ final ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
// Load raw contact IDs for all contacts involved.
- long rawContactIds[] = getRawContactIdsForAggregation(contactIds);
+ final long rawContactIds[] = getRawContactIdsForAggregation(contactIds);
+ final long[][] separatedRawContactIds = getSeparatedRawContactIds(contactIds);
if (rawContactIds == null) {
Log.e(TAG, "Invalid arguments for joinSeveralContacts request");
return;
@@ -1193,21 +1293,33 @@
}
// Before we get to 500 we need to flush the operations list
if (operations.size() > 0 && operations.size() % batchSize == 0) {
- if (!applyJoinOperations(resolver, operations)) {
+ if (!applyOperations(resolver, operations)) {
+ if (receiver != null) {
+ receiver.send(CP2_ERROR, new Bundle());
+ }
return;
}
operations.clear();
}
}
}
- if (operations.size() > 0 && !applyJoinOperations(resolver, operations)) {
+ if (operations.size() > 0 && !applyOperations(resolver, operations)) {
+ if (receiver != null) {
+ receiver.send(CP2_ERROR, new Bundle());
+ }
return;
}
- showToast(R.string.contactsJoinedMessage);
+ if (receiver != null) {
+ final Bundle result = new Bundle();
+ result.putSerializable(EXTRA_RAW_CONTACT_IDS, separatedRawContactIds);
+ receiver.send(CONTACTS_LINKED, result);
+ } else {
+ showToast(R.string.contactsJoinedMessage);
+ }
}
/** Returns true if the batch was successfully applied and false otherwise. */
- private boolean applyJoinOperations(ContentResolver resolver,
+ private boolean applyOperations(ContentResolver resolver,
ArrayList<ContentProviderOperation> operations) {
try {
resolver.applyBatch(ContactsContract.AUTHORITY, operations);
@@ -1219,7 +1331,6 @@
}
}
-
private void joinContacts(Intent intent) {
long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
@@ -1296,13 +1407,61 @@
deliverCallback(callbackIntent);
}
+ /**
+ * Gets the raw contact ids for each contact id in {@param contactIds}. Each index of the outer
+ * array of the return value holds an array of raw contact ids for one contactId.
+ * @param contactIds
+ * @return
+ */
+ private long[][] getSeparatedRawContactIds(long[] contactIds) {
+ final long[][] rawContactIds = new long[contactIds.length][];
+ for (int i = 0; i < contactIds.length; i++) {
+ rawContactIds[i] = getRawContactIds(contactIds[i]);
+ }
+ return rawContactIds;
+ }
+
+ /**
+ * Gets the raw contact ids associated with {@param contactId}.
+ * @param contactId
+ * @return Array of raw contact ids.
+ */
+ private long[] getRawContactIds(long contactId) {
+ final ContentResolver resolver = getContentResolver();
+ long rawContactIds[];
+
+ final StringBuilder queryBuilder = new StringBuilder();
+ queryBuilder.append(RawContacts.CONTACT_ID)
+ .append("=")
+ .append(String.valueOf(contactId));
+
+ final Cursor c = resolver.query(RawContacts.CONTENT_URI,
+ JoinContactQuery.PROJECTION,
+ queryBuilder.toString(),
+ null, null);
+ if (c == null) {
+ Log.e(TAG, "Unable to open Contacts DB cursor");
+ return null;
+ }
+ try {
+ rawContactIds = new long[c.getCount()];
+ for (int i = 0; i < rawContactIds.length; i++) {
+ c.moveToPosition(i);
+ final long rawContactId = c.getLong(JoinContactQuery._ID);
+ rawContactIds[i] = rawContactId;
+ }
+ } finally {
+ c.close();
+ }
+ return rawContactIds;
+ }
+
private long[] getRawContactIdsForAggregation(long[] contactIds) {
if (contactIds == null) {
return null;
}
final ContentResolver resolver = getContentResolver();
- long rawContactIds[];
final StringBuilder queryBuilder = new StringBuilder();
final String stringContactIds[] = new String[contactIds.length];
@@ -1327,6 +1486,7 @@
showToast(R.string.contactSavedErrorToast);
return null;
}
+ long rawContactIds[];
try {
if (c.getCount() < 2) {
Log.e(TAG, "Not enough raw contacts to aggregate together.");
@@ -1362,6 +1522,19 @@
}
/**
+ * Construct a {@link AggregationExceptions#TYPE_KEEP_SEPARATE} ContentProviderOperation.
+ */
+ private void buildSplitContactDiff(ArrayList<ContentProviderOperation> operations,
+ long rawContactId1, long rawContactId2) {
+ final Builder builder =
+ ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
+ builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_SEPARATE);
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
+ operations.add(builder.build());
+ }
+
+ /**
* Shows a toast on the UI thread.
*/
private void showToast(final int message) {
diff --git a/src/com/android/contacts/GroupMetaDataLoader.java b/src/com/android/contacts/GroupMetaDataLoader.java
index 47648b3..9bfcd42 100644
--- a/src/com/android/contacts/GroupMetaDataLoader.java
+++ b/src/com/android/contacts/GroupMetaDataLoader.java
@@ -50,7 +50,7 @@
public GroupMetaDataLoader(Context context, Uri groupUri) {
super(context, ensureIsGroupUri(groupUri), COLUMNS, Groups.ACCOUNT_TYPE + " NOT NULL AND "
- + Groups.ACCOUNT_NAME + " NOT NULL", null, Groups.TITLE);
+ + Groups.ACCOUNT_NAME + " NOT NULL", null, Groups.TITLE + " COLLATE NOCASE");
}
/**
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 2d6740d..7042121 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -110,6 +110,8 @@
private boolean mShowHomeIcon;
private boolean mShowHomeAsUp;
+ private int mSearchHintResId;
+
public interface TabState {
public static int ALL = 0;
@@ -121,6 +123,12 @@
public ActionBarAdapter(Activity activity, Listener listener, ActionBar actionBar,
View portraitTabs, View landscapeTabs, Toolbar toolbar) {
+ this(activity, listener, actionBar, portraitTabs, landscapeTabs, toolbar,
+ R.string.hint_findContacts);
+ }
+
+ public ActionBarAdapter(Activity activity, Listener listener, ActionBar actionBar,
+ View portraitTabs, View landscapeTabs, Toolbar toolbar, int searchHintResId) {
mActivity = activity;
mListener = listener;
mActionBar = actionBar;
@@ -130,6 +138,7 @@
mToolbar = toolbar;
mToolBarFrame = (FrameLayout) mToolbar.getParent();
mMaxToolbarContentInsetStart = mToolbar.getContentInsetStart();
+ mSearchHintResId = searchHintResId;
setupSearchAndSelectionViews();
setupTabs(mActivity);
@@ -143,6 +152,10 @@
mShowHomeAsUp = showHomeAsUp;
}
+ public EditText getSearchView() {
+ return mSearchView;
+ }
+
private void setupTabs(Context context) {
final TypedArray attributeArray = context.obtainStyledAttributes(
new int[]{android.R.attr.actionBarSize});
@@ -163,7 +176,7 @@
mSearchContainer.setBackgroundColor(mActivity.getResources().getColor(
R.color.searchbox_background_color));
mSearchView = (EditText) mSearchContainer.findViewById(R.id.search_view);
- mSearchView.setHint(mActivity.getString(R.string.hint_findContacts));
+ mSearchView.setHint(mActivity.getString(mSearchHintResId));
mSearchView.addTextChangedListener(new SearchTextWatcher());
mSearchContainer.findViewById(R.id.search_back_button).setOnClickListener(
new OnClickListener() {
diff --git a/src/com/android/contacts/activities/GroupEditorActivity.java b/src/com/android/contacts/activities/GroupEditorActivity.java
index 8a285a7..e316902 100644
--- a/src/com/android/contacts/activities/GroupEditorActivity.java
+++ b/src/com/android/contacts/activities/GroupEditorActivity.java
@@ -22,6 +22,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
+import android.widget.AutoCompleteTextView;
import com.android.contacts.ContactsActivity;
import com.android.contacts.R;
@@ -30,6 +31,7 @@
import com.android.contacts.quickcontact.QuickContactActivity;
import com.android.contacts.util.DialogManager;
+// TODO(wjang): it longer works. will be deleted shortly
public class GroupEditorActivity extends ContactsActivity
implements DialogManager.DialogShowingViewActivity {
@@ -63,13 +65,13 @@
mFragment = (GroupEditorFragment) getFragmentManager().findFragmentById(
R.id.group_editor_fragment);
mFragment.setListener(mFragmentListener);
- mFragment.setContentResolver(getContentResolver());
+ // mFragment.setContentResolver(getContentResolver());
// NOTE The fragment will restore its state by itself after orientation changes, so
// we need to do this only for a new instance.
if (savedState == null) {
Uri uri = Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
- mFragment.load(action, uri, getIntent().getExtras());
+ // mFragment.load(action, uri, getIntent().getExtras());
}
}
@@ -139,6 +141,20 @@
startActivity(ImplicitIntentsUtil.composeQuickContactIntent(
contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED));
}
+
+ @Override
+ public AutoCompleteTextView getSearchView() {
+ return null;
+ }
+
+ @Override
+ public boolean isSearchMode() {
+ return false;
+ }
+
+ @Override
+ public void setSearchMode(boolean searchMode) {
+ }
};
@Override
diff --git a/src/com/android/contacts/activities/GroupMembersActivity.java b/src/com/android/contacts/activities/GroupMembersActivity.java
index e13bcd6..7ebb3e0 100644
--- a/src/com/android/contacts/activities/GroupMembersActivity.java
+++ b/src/com/android/contacts/activities/GroupMembersActivity.java
@@ -15,59 +15,511 @@
*/
package com.android.contacts.activities;
-import android.app.ActionBar;
+import android.app.Fragment;
import android.app.FragmentManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.CursorLoader;
import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.AutoCompleteTextView;
+import android.widget.Toast;
-import com.android.contacts.ContactsActivity;
+import com.android.contacts.AppCompatContactsActivity;
+import com.android.contacts.ContactSaveService;
+import com.android.contacts.GroupListLoader;
+import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.util.ImplicitIntentsUtil;
+import com.android.contacts.group.GroupEditorFragment;
import com.android.contacts.group.GroupMembersListFragment;
-import com.android.contacts.group.GroupMembersListFragment.GroupMembersListCallbacks;
+import com.android.contacts.group.GroupMetadata;
+import com.android.contacts.group.GroupUtil;
+import com.android.contacts.interactions.GroupDeletionDialogFragment;
+import com.android.contacts.list.ContactsRequest;
+import com.android.contacts.list.MultiSelectContactsListFragment;
import com.android.contacts.quickcontact.QuickContactActivity;
-/** Displays the members of a group. */
-public class GroupMembersActivity extends ContactsActivity implements GroupMembersListCallbacks {
+/**
+ * Displays the members of a group and allows the user to edit it.
+ */
+// TODO(wjang): rename it to GroupActivity since it does both display and edit now.
+public class GroupMembersActivity extends AppCompatContactsActivity implements
+ ActionBarAdapter.Listener,
+ MultiSelectContactsListFragment.OnCheckBoxListActionListener,
+ GroupMembersListFragment.GroupMembersListListener,
+ GroupEditorFragment.Listener {
- private static final String TAG_GROUP_MEMBERS = "group_members";
+ private static final String TAG = "GroupMembersActivity";
- private GroupMembersListFragment mFragment;
+ private static final boolean DEBUG = false;
+
+ private static final String KEY_GROUP_METADATA = "groupMetadata";
+
+ private static final int LOADER_GROUP_METADATA = 0;
+ private static final int LOADER_GROUP_LIST_DETAILS = 1;
+
+ private static final int FRAGMENT_MEMBERS_LIST = -1;
+ private static final int FRAGMENT_EDITOR = -2;
+
+ public static final String ACTION_SAVE_COMPLETED = "saveCompleted";
+
+ private class GroupPagerAdapter extends FragmentPagerAdapter {
+
+ public GroupPagerAdapter(FragmentManager fragmentManager) {
+ super(fragmentManager);
+ }
+
+ @Override
+ public int getCount() {
+ return mIsInsertAction ? 1 : 2;
+ }
+
+ public Fragment getItem(int position) {
+ if (mIsInsertAction) {
+ switch (position) {
+ case 0:
+ mEditorFragment = GroupEditorFragment.newInstance(
+ Intent.ACTION_INSERT, mGroupMetadata, getIntent().getExtras());
+ return mEditorFragment;
+ }
+ throw new IllegalStateException("Unhandled position " + position);
+ } else {
+ switch (position) {
+ case 0:
+ mMembersListFragment = GroupMembersListFragment.newInstance(mGroupMetadata);
+ return mMembersListFragment;
+ case 1:
+ // TODO: double check what intent extras need to be supported
+ mEditorFragment = GroupEditorFragment.newInstance(
+ Intent.ACTION_EDIT, mGroupMetadata, getIntent().getExtras());
+ return mEditorFragment;
+ }
+ throw new IllegalStateException("Unhandled position " + position);
+ }
+ }
+
+ private boolean isCurrentItem(int fragment) {
+ if (mIsInsertAction) {
+ return FRAGMENT_EDITOR == fragment;
+ }
+ int currentItem = mViewPager.getCurrentItem();
+ switch (fragment) {
+ case FRAGMENT_MEMBERS_LIST:
+ return currentItem == 0;
+ case FRAGMENT_EDITOR:
+ return currentItem == 1;
+ }
+ return false;
+ }
+
+ private void setCurrentItem(int fragment) {
+ if (mIsInsertAction) {
+ switch (fragment) {
+ case FRAGMENT_EDITOR:
+ mViewPager.setCurrentItem(0);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported fragment " + fragment);
+ }
+ } else {
+ switch (fragment) {
+ case FRAGMENT_MEMBERS_LIST:
+ mViewPager.setCurrentItem(0);
+ break;
+ case FRAGMENT_EDITOR:
+ mViewPager.setCurrentItem(1);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported fragment " + fragment);
+ }
+ }
+ }
+ }
+
+ /** Step 1 of loading group metadata. */
+ private final LoaderCallbacks<Cursor> mGroupMetadataCallbacks = new LoaderCallbacks<Cursor>() {
+
+ @Override
+ public CursorLoader onCreateLoader(int id, Bundle args) {
+ return new GroupMetaDataLoader(GroupMembersActivity.this, mGroupUri);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ if (cursor == null || cursor.isClosed()) {
+ Log.e(TAG, "Failed to load group metadata");
+ return;
+ }
+ if (cursor.moveToNext()) {
+ final boolean deleted = cursor.getInt(GroupMetaDataLoader.DELETED) == 1;
+ if (!deleted) {
+ mGroupMetadata = new GroupMetadata();
+ mGroupMetadata.uri = mGroupUri;
+ mGroupMetadata.accountName = cursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
+ mGroupMetadata.accountType = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
+ mGroupMetadata.dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
+ mGroupMetadata.groupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID);
+ mGroupMetadata.groupName = cursor.getString(GroupMetaDataLoader.TITLE);
+ mGroupMetadata.readOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1;
+
+ final AccountTypeManager accountTypeManager =
+ AccountTypeManager.getInstance(GroupMembersActivity.this);
+ final AccountType accountType = accountTypeManager.getAccountType(
+ mGroupMetadata.accountType, mGroupMetadata.dataSet);
+ mGroupMetadata.editable = accountType.isGroupMembershipEditable();
+
+ getLoaderManager().restartLoader(LOADER_GROUP_LIST_DETAILS, null,
+ mGroupListCallbacks);
+ }
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {}
+ };
+
+ /** Step 2 of loading group metadata. */
+ private final LoaderCallbacks<Cursor> mGroupListCallbacks = new LoaderCallbacks<Cursor>() {
+
+ @Override
+ public CursorLoader onCreateLoader(int id, Bundle args) {
+ final GroupListLoader groupListLoader = new GroupListLoader(GroupMembersActivity.this);
+
+ // TODO(wjang): modify GroupListLoader to accept this selection criteria more naturally
+ groupListLoader.setSelection(groupListLoader.getSelection()
+ + " AND " + ContactsContract.Groups._ID + "=?");
+
+ final String[] selectionArgs = new String[1];
+ selectionArgs[0] = Long.toString(mGroupMetadata.groupId);
+ groupListLoader.setSelectionArgs(selectionArgs);
+
+ return groupListLoader;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ if (cursor == null || cursor.isClosed()) {
+ Log.e(TAG, "Failed to load group list details");
+ return;
+ }
+ if (cursor.moveToNext()) {
+ mGroupMetadata.memberCount = cursor.getInt(GroupListLoader.MEMBER_COUNT);
+ }
+ onGroupMetadataLoaded();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {}
+ };
+
+ private ActionBarAdapter mActionBarAdapter;
+ private ViewPager mViewPager;
+
+ private GroupPagerAdapter mPagerAdapter;
+
+ private Uri mGroupUri;
+ private GroupMetadata mGroupMetadata;
+
+ private GroupMembersListFragment mMembersListFragment;
+ private GroupEditorFragment mEditorFragment;
+
+ private boolean mIsInsertAction;
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
+ mIsInsertAction = Intent.ACTION_INSERT.equals(getIntent().getAction());
+
+ mGroupUri = getIntent().getData();
+ if (savedState != null) {
+ mGroupMetadata = savedState.getParcelable(KEY_GROUP_METADATA);
+ }
+
+ // Setup the view
setContentView(R.layout.group_members_activity);
+ mViewPager = (ViewPager) findViewById(R.id.view_pager);
- final ActionBar actionBar = getActionBar();
- if (actionBar != null) {
- actionBar.setDisplayShowHomeEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setDisplayShowTitleEnabled(true);
- }
-
- final FragmentManager fragmentManager = getFragmentManager();
- mFragment = (GroupMembersListFragment) fragmentManager.findFragmentByTag(TAG_GROUP_MEMBERS);
- if (mFragment == null) {
- mFragment = new GroupMembersListFragment();
- fragmentManager.beginTransaction()
- .add(R.id.fragment_container, mFragment, TAG_GROUP_MEMBERS)
- .commit();
- }
- mFragment.setGroupUri(getIntent().getData());
- mFragment.setCallbacks(this);
+ // Set up the action bar
+ final Toolbar toolbar = getView(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ final ContactsRequest contactsRequest = new ContactsRequest();
+ contactsRequest.setActionCode(ContactsRequest.ACTION_GROUP);
+ mActionBarAdapter = new ActionBarAdapter(this, this, getSupportActionBar(),
+ /* portraitTabs */ null, /* landscapeTabs */ null, toolbar,
+ R.string.enter_contact_name);
+ mActionBarAdapter.setShowHomeIcon(true);
+ mActionBarAdapter.setShowHomeAsUp(true);
+ mActionBarAdapter.initialize(savedState, contactsRequest);
}
@Override
- public void onHomePressed() {
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(KEY_GROUP_METADATA, mGroupMetadata);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ if (mIsInsertAction) {
+ mGroupMetadata = new GroupMetadata();
+ onGroupMetadataLoaded();
+ } else {
+ if (mGroupMetadata == null) {
+ getLoaderManager().restartLoader(
+ LOADER_GROUP_METADATA, null, mGroupMetadataCallbacks);
+ } else {
+ onGroupMetadataLoaded();
+ }
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent newIntent) {
+ super.onNewIntent(newIntent);
+
+ if (ACTION_SAVE_COMPLETED.equals(newIntent.getAction())) {
+ final Uri groupUri = newIntent.getData();
+ if (groupUri == null) {
+ Toast.makeText(this, R.string.groupSavedErrorToast, Toast.LENGTH_SHORT).show();
+ setResult(RESULT_CANCELED);
+ finish();
+ } else {
+ Toast.makeText(this, R.string.groupSavedToast,Toast.LENGTH_SHORT).show();
+
+ final Intent intent = GroupUtil.createViewGroupIntent(this, groupUri);
+ finish();
+ startActivity(intent);
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mGroupMetadata == null || mGroupMetadata.memberCount < 0) {
+ // Hide menu options until metatdata is fully loaded
+ return false;
+ }
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.view_group, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ final boolean isSelectionMode = mActionBarAdapter.isSelectionMode();
+ final boolean isSearchMode = false;
+
+ final boolean isListFragment = mPagerAdapter.isCurrentItem(FRAGMENT_MEMBERS_LIST);
+ final boolean isEditorFragment = mPagerAdapter.isCurrentItem(FRAGMENT_EDITOR);
+
+ final boolean isGroupEditable = mGroupMetadata.editable;
+ final boolean isGroupReadOnly = mGroupMetadata.readOnly;
+
+ setVisible(menu, R.id.menu_edit_group, isGroupEditable && !isEditorFragment &&
+ !isSelectionMode && !isSearchMode);
+
+ setVisible(menu, R.id.menu_delete_group, !isGroupReadOnly && !isEditorFragment &&
+ !isSelectionMode && !isSearchMode);
+
+ setVisible(menu, R.id.menu_remove_from_group,
+ isGroupEditable && isSelectionMode && isListFragment);
+
+ return true;
+ }
+
+ private static void setVisible(Menu menu, int id, boolean visible) {
+ final MenuItem menuItem = menu.findItem(id);
+ if (menuItem != null) {
+ menuItem.setVisible(visible);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home: {
+ onBackPressed();
+ return true;
+ }
+ case R.id.menu_edit_group: {
+ mPagerAdapter.setCurrentItem(FRAGMENT_EDITOR);
+ return true;
+ }
+ case R.id.menu_delete_group: {
+ GroupDeletionDialogFragment.show(getFragmentManager(), mGroupMetadata.groupId,
+ mGroupMetadata.groupName, /* endActivity */ true);
+ return true;
+ }
+ case R.id.menu_remove_from_group: {
+ removeSelectedContacts();
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void removeSelectedContacts() {
+ final long[] rawContactsToRemove =
+ mMembersListFragment.getAdapter().getSelectedContactIdsArray();
+ final Intent intent = ContactSaveService.createGroupUpdateIntent(
+ this, mGroupMetadata.groupId, /* groupName */ null,
+ /* rawContactsToAdd */ null, rawContactsToRemove, getClass(),
+ GroupMembersActivity.ACTION_SAVE_COMPLETED);
+ startService(intent);
+ }
+
+ private void onGroupMetadataLoaded() {
+ if (DEBUG) Log.d(TAG, "Loaded " + mGroupMetadata);
+
+ if (mPagerAdapter == null) {
+ mPagerAdapter = new GroupPagerAdapter(getFragmentManager());
+ mViewPager.setAdapter(mPagerAdapter);
+ }
+
+ if (mIsInsertAction) {
+ mPagerAdapter.setCurrentItem(FRAGMENT_EDITOR);
+ getSupportActionBar().setTitle(getString(R.string.editGroupDescription));
+ } else {
+ getSupportActionBar().setTitle(mGroupMetadata.groupName);
+ mPagerAdapter.setCurrentItem(FRAGMENT_MEMBERS_LIST);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mIsInsertAction) {
+ finish();
+ } else if (mActionBarAdapter.isSelectionMode()) {
+ mActionBarAdapter.setSelectionMode(false);
+ if (mMembersListFragment != null) {
+ mMembersListFragment.displayCheckBoxes(false);
+ }
+ } else if (mActionBarAdapter.isSearchMode()) {
+ mActionBarAdapter.setSearchMode(false);
+ } else if (mPagerAdapter.isCurrentItem(FRAGMENT_EDITOR)) {
+ mPagerAdapter.setCurrentItem(FRAGMENT_MEMBERS_LIST);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ // GroupsMembersListFragment callbacks
+
+ @Override
+ public void onGroupMemberListItemClicked(Uri contactLookupUri) {
+ startActivity(ImplicitIntentsUtil.composeQuickContactIntent(
+ contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED));
+ }
+
+ // ActionBarAdapter callbacks
+
+ @Override
+ public void onAction(int action) {
+ switch (action) {
+ case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
+ mActionBarAdapter.setSearchMode(true);
+ invalidateOptionsMenu();
+ showFabWithAnimation(/* showFabWithAnimation = */ false);
+ break;
+ case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
+ if (mMembersListFragment != null) {
+ mMembersListFragment.displayCheckBoxes(true);
+ }
+ invalidateOptionsMenu();
+ break;
+ case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
+ mActionBarAdapter.setSearchMode(false);
+ if (mMembersListFragment != null) {
+ mMembersListFragment.displayCheckBoxes(false);
+ }
+ invalidateOptionsMenu();
+ showFabWithAnimation(/* showFabWithAnimation */ true);
+ break;
+ case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE:
+ showFabWithAnimation(/* showFabWithAnimation */ true);
+ break;
+ }
+ }
+
+ private void showFabWithAnimation(boolean showFab) {
+ // TODO(wjang): b/28497108
+ }
+
+ @Override
+ public void onSelectedTabChanged() {
+ }
+
+ @Override
+ public void onUpButtonPressed() {
onBackPressed();
}
+ // MultiSelect checkbox callbacks
+
@Override
- public void onGroupNameLoaded(String groupName) {
- setTitle(groupName);
+ public void onStartDisplayingCheckBoxes() {
+ mActionBarAdapter.setSelectionMode(true);
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onSelectedContactIdsChanged() {
+ if (mActionBarAdapter.isSelectionMode() && mMembersListFragment != null) {
+ mActionBarAdapter.setSelectionCount(
+ mMembersListFragment.getSelectedContactIds().size());
+ }
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onStopDisplayingCheckBoxes() {
+ mActionBarAdapter.setSelectionMode(false);
+ invalidateOptionsMenu();
+ }
+
+ // GroupEditorFragment.Listener callbacks
+
+ @Override
+ public void onGroupNotFound() {
+ finish();
+ }
+
+ @Override
+ public void onReverted() {
+ if (mIsInsertAction) {
+ finish();
+ } else {
+ mPagerAdapter.setCurrentItem(FRAGMENT_MEMBERS_LIST);
+ }
+ }
+
+ @Override
+ public void onSaveFinished(int resultCode, Intent resultIntent) {
+ if (mIsInsertAction) {
+ final Intent intent = GroupUtil.createViewGroupIntent(this, resultIntent.getData());
+ finish();
+ startActivity(intent);
+ }
+ }
+
+ @Override
+ public void onAccountsNotFound() {
+ finish();
}
@Override
@@ -77,10 +529,20 @@
}
@Override
- public void onEditGroup(Uri groupUri) {
- final Intent intent = new Intent(this, GroupEditorActivity.class);
- intent.setData(groupUri);
- intent.setAction(Intent.ACTION_EDIT);
- startActivity(intent);
+ public AutoCompleteTextView getSearchView() {
+ return mActionBarAdapter == null
+ ? null : (AutoCompleteTextView) mActionBarAdapter.getSearchView();
+ }
+
+ @Override
+ public boolean isSearchMode() {
+ return mActionBarAdapter == null ? false : mActionBarAdapter.isSearchMode();
+ }
+
+ @Override
+ public void setSearchMode(boolean searchMode) {
+ if (mActionBarAdapter != null) {
+ mActionBarAdapter.setSearchMode(searchMode);
+ }
}
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 002d74b..93d3f84 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -94,7 +94,7 @@
import com.android.contacts.list.ContactsIntentResolver;
import com.android.contacts.list.ContactsRequest;
import com.android.contacts.list.ContactsUnavailableFragment;
-import com.android.contacts.list.MultiSelectContactsListFragment;
+import com.android.contacts.list.DefaultContactBrowseListFragment;
import com.android.contacts.list.MultiSelectContactsListFragment.OnCheckBoxListActionListener;
import com.android.contacts.list.OnContactBrowserActionListener;
import com.android.contacts.list.OnContactsUnavailableActionListener;
@@ -150,7 +150,7 @@
/**
* Showing a list of Contacts. Also used for showing search results in search mode.
*/
- private MultiSelectContactsListFragment mAllFragment;
+ private DefaultContactBrowseListFragment mAllFragment;
private GroupsFragment mGroupsFragment;
private AccountFiltersFragment mAccountFiltersFragment;
@@ -376,7 +376,7 @@
// However, if it's after screen rotation, the fragments have been re-created by
// the fragment manager, so first see if there're already the target fragments
// existing.
- mAllFragment = (MultiSelectContactsListFragment)
+ mAllFragment = (DefaultContactBrowseListFragment)
fragmentManager.findFragmentByTag(ALL_TAG);
mGroupsFragment = (GroupsFragment)
fragmentManager.findFragmentByTag(GROUPS_TAG);
@@ -384,7 +384,7 @@
fragmentManager.findFragmentByTag(FILTERS_TAG);
if (mAllFragment == null) {
- mAllFragment = new MultiSelectContactsListFragment();
+ mAllFragment = new DefaultContactBrowseListFragment();
transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG);
if (areGroupWritableAccountsAvailable()) {
@@ -1316,14 +1316,8 @@
}
private void joinSelectedContacts() {
- final Long[] contactIdsArray = mAllFragment.getSelectedContactIds().toArray(
- new Long[mAllFragment.getSelectedContactIds().size()]);
- final long[] contactIdsArray2 = new long[contactIdsArray.length];
- for (int i = 0; i < contactIdsArray.length; i++) {
- contactIdsArray2[i] = contactIdsArray[i];
- }
- final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(this,
- contactIdsArray2);
+ final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(
+ this, mAllFragment.getSelectedContactIdsArray());
this.startService(intent);
mActionBarAdapter.setSelectionMode(false);
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index 25a07f1..a7d30a4 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -25,7 +25,6 @@
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.content.DialogInterface;
@@ -59,9 +58,8 @@
import com.android.contacts.ContactSaveService;
import com.android.contacts.GroupMemberLoader;
import com.android.contacts.GroupMemberLoader.GroupEditorQuery;
-import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
-import com.android.contacts.activities.GroupEditorActivity;
+import com.android.contacts.activities.GroupMembersActivity;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
import com.android.contacts.common.model.account.AccountType;
@@ -79,11 +77,13 @@
public class GroupEditorFragment extends Fragment implements SelectAccountDialogFragment.Listener {
private static final String TAG = "GroupEditorFragment";
- private static final String LEGACY_CONTACTS_AUTHORITY = "contacts";
+ private static final String ARG_ACTION = "action";
+ private static final String ARG_GROUP_METADATA = "groupMetadata";
+ private static final String ARG_INTENT_EXTRAS = "intentExtras";
private static final String KEY_ACTION = "action";
- private static final String KEY_GROUP_URI = "groupUri";
- private static final String KEY_GROUP_ID = "groupId";
+ private static final String KEY_GROUP_METADATA = "groupMetadata";
+ private static final String KEY_INTENT_EXTRAS = "intentExtras";
private static final String KEY_STATUS = "status";
private static final String KEY_ACCOUNT_NAME = "accountName";
private static final String KEY_ACCOUNT_TYPE = "accountType";
@@ -96,11 +96,11 @@
private static final String CURRENT_EDITOR_TAG = "currentEditorForAccount";
- public static interface Listener {
+ public interface Listener {
/**
* Group metadata was not found, close the fragment now.
*/
- public void onGroupNotFound();
+ void onGroupNotFound();
/**
* User has tapped Revert, close the fragment now.
@@ -121,9 +121,26 @@
* Group member name or photo was clicked in order to view contact details.
*/
void onGroupMemberClicked(Uri contactLookupUri);
+
+ // TODO(wjang): consider calling these directly on the host Activity or moving these
+ // options menu items to the host Activity.
+
+ /**
+ * Returns the autocomplete view from the action bar.
+ */
+ AutoCompleteTextView getSearchView();
+
+ /**
+ * Whether the action bar is currently in search mode.
+ */
+ boolean isSearchMode();
+
+ /**
+ * Change whether the action bar is in search mode.
+ */
+ void setSearchMode(boolean searchMode);
}
- private static final int LOADER_GROUP_METADATA = 1;
private static final int LOADER_EXISTING_MEMBERS = 2;
private static final int LOADER_NEW_GROUP_MEMBER = 3;
@@ -165,7 +182,6 @@
*/
public enum Status {
SELECTING_ACCOUNT, // Account select dialog is showing
- LOADING, // Loader is fetching the group metadata
EDITING, // Not currently busy. We are waiting forthe user to enter data.
SAVING, // Data is currently being saved
CLOSING // Prevents any more saves
@@ -174,8 +190,8 @@
private Context mContext;
private String mAction;
private Bundle mIntentExtras;
- private Uri mGroupUri;
- private long mGroupId;
+ private GroupMetadata mGroupMetadata;
+
private Listener mListener;
private Status mStatus;
@@ -205,41 +221,72 @@
private ArrayList<Member> mListMembersToRemove = new ArrayList<Member>();
private ArrayList<Member> mListToDisplay = new ArrayList<Member>();
- public GroupEditorFragment() {
+ public static GroupEditorFragment newInstance(String action, GroupMetadata groupMetadata,
+ Bundle intentExtras) {
+ final Bundle args = new Bundle();
+ args.putString(ARG_ACTION, action);
+ args.putParcelable(ARG_GROUP_METADATA, groupMetadata);
+ args.putParcelable(ARG_INTENT_EXTRAS, intentExtras);
+ final GroupEditorFragment fragment = new GroupEditorFragment();
+ fragment.setArguments(args);
+ return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
setHasOptionsMenu(true);
+
mLayoutInflater = inflater;
mRootView = (ViewGroup) inflater.inflate(R.layout.group_editor_fragment, container, false);
return mRootView;
}
@Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mContext = activity;
- mPhotoManager = ContactPhotoManager.getInstance(mContext);
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState == null) {
+ final Bundle args = getArguments();
+ if (args == null) {
+ throw new IllegalStateException("Group editor fragment created without arguments");
+ }
+ mAction = args.getString(ARG_ACTION);
+ mGroupMetadata = args.getParcelable(ARG_GROUP_METADATA);
+ mIntentExtras = args.getParcelable(ARG_INTENT_EXTRAS);
+ } else {
+ onRestoreInstanceState(savedInstanceState);
+ }
+
mMemberListAdapter = new MemberListAdapter();
}
@Override
+ public void onAttach(Activity context) {
+ super.onAttach(context);
+ mContext = context;
+ mPhotoManager = ContactPhotoManager.getInstance(mContext);
+ mContentResolver = mContext.getContentResolver();
+
+ try {
+ mListener = (Listener) getActivity();
+ } catch (ClassCastException e) {
+ throw new ClassCastException(getActivity() + " must implement " +
+ Listener.class.getSimpleName());
+ }
+ }
+
+ @Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
- // Just restore from the saved state. No loading.
- onRestoreInstanceState(savedInstanceState);
- if (mStatus == Status.SELECTING_ACCOUNT) {
- // Account select dialog is showing. Don't setup the editor yet.
- } else if (mStatus == Status.LOADING) {
- startGroupMetaDataLoader();
- } else {
+ if (mStatus != Status.SELECTING_ACCOUNT) {
setupEditorForAccount();
- }
+ } // else Account select dialog is showing. Don't setup the editor yet.
} else if (Intent.ACTION_EDIT.equals(mAction)) {
- startGroupMetaDataLoader();
+ bindGroupMetaData();
+ // Load existing members
+ getLoaderManager().initLoader(
+ LOADER_EXISTING_MEMBERS, null, mGroupMemberListLoaderListener);
} else if (Intent.ACTION_INSERT.equals(mAction)) {
final Account account = mIntentExtras == null ? null :
(Account) mIntentExtras.getParcelable(Intents.Insert.EXTRA_ACCOUNT);
@@ -256,24 +303,15 @@
// No Account specified. Let the user choose from a disambiguation dialog.
selectAccountAndCreateGroup();
}
- } else {
- throw new IllegalArgumentException("Unknown Action String " + mAction +
- ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
}
}
- private void startGroupMetaDataLoader() {
- mStatus = Status.LOADING;
- getLoaderManager().initLoader(LOADER_GROUP_METADATA, null,
- mGroupMetaDataLoaderListener);
- }
-
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_ACTION, mAction);
- outState.putParcelable(KEY_GROUP_URI, mGroupUri);
- outState.putLong(KEY_GROUP_ID, mGroupId);
+ outState.putParcelable(KEY_GROUP_METADATA, mGroupMetadata);
+ outState.putParcelable(KEY_INTENT_EXTRAS, mIntentExtras);
outState.putSerializable(KEY_STATUS, mStatus);
outState.putString(KEY_ACCOUNT_NAME, mAccountName);
@@ -290,8 +328,8 @@
private void onRestoreInstanceState(Bundle state) {
mAction = state.getString(KEY_ACTION);
- mGroupUri = state.getParcelable(KEY_GROUP_URI);
- mGroupId = state.getLong(KEY_GROUP_ID);
+ mGroupMetadata = state.getParcelable(KEY_GROUP_METADATA);
+ mIntentExtras = state.getParcelable(KEY_INTENT_EXTRAS);
mStatus = (Status) state.getSerializable(KEY_STATUS);
mAccountName = state.getString(KEY_ACCOUNT_NAME);
@@ -306,13 +344,6 @@
mListToDisplay = state.getParcelableArrayList(KEY_MEMBERS_TO_DISPLAY);
}
- public void setContentResolver(ContentResolver resolver) {
- mContentResolver = resolver;
- if (mAutoCompleteAdapter != null) {
- mAutoCompleteAdapter.setContentResolver(mContentResolver);
- }
- }
-
private void selectAccountAndCreateGroup() {
final List<AccountWithDataSet> accounts =
AccountTypeManager.getInstance(mContext).getAccounts(true /* writeable */);
@@ -400,13 +431,17 @@
} else {
editorView = mRootView.findViewWithTag(CURRENT_EDITOR_TAG);
if (editorView == null) {
- throw new IllegalStateException("Group editor view not found");
+ // TODO(wjang): should not happen once this is fully integrated into group members
+ // activity so just let it go for now
+ // throw new IllegalStateException("Group editor view not found");
+ return;
}
}
mGroupNameView = (TextView) editorView.findViewById(R.id.group_name);
- mAutoCompleteTextView = (AutoCompleteTextView) editorView.findViewById(
- R.id.add_member_field);
+ if (mListener != null) {
+ mAutoCompleteTextView = mListener.getSearchView();
+ }
mListView = (ListView) editorView.findViewById(android.R.id.list);
mListView.setAdapter(mMemberListAdapter);
@@ -466,26 +501,12 @@
mStatus = Status.EDITING;
}
- public void load(String action, Uri groupUri, Bundle intentExtras) {
- mAction = action;
- mGroupUri = groupUri;
- mGroupId = (groupUri != null) ? ContentUris.parseId(mGroupUri) : 0;
- mIntentExtras = intentExtras;
- }
-
- private void bindGroupMetaData(Cursor cursor) {
- if (!cursor.moveToFirst()) {
- Log.i(TAG, "Group not found with URI: " + mGroupUri + " Closing activity now.");
- if (mListener != null) {
- mListener.onGroupNotFound();
- }
- return;
- }
- mOriginalGroupName = cursor.getString(GroupMetaDataLoader.TITLE);
- mAccountName = cursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
- mAccountType = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
- mDataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
- mGroupNameIsReadOnly = (cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1);
+ private void bindGroupMetaData() {
+ mOriginalGroupName = mGroupMetadata.groupName;
+ mAccountName = mGroupMetadata.accountName;
+ mAccountType = mGroupMetadata.accountType;
+ mDataSet = mGroupMetadata.dataSet;
+ mGroupNameIsReadOnly = mGroupMetadata.readOnly;
setupEditorForAccount();
// Setup the group metadata display
@@ -518,12 +539,36 @@
}
@Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ final boolean isSearchMode = mListener == null ? false : mListener.isSearchMode();
+ setVisible(menu, R.id.menu_add, !isSearchMode);
+ setVisible(menu, R.id.menu_save, !isSearchMode);
+ setVisible(menu, R.id.menu_discard, !isSearchMode);
+ }
+
+ private static void setVisible(Menu menu, int id, boolean visible) {
+ final MenuItem menuItem = menu.findItem(id);
+ if (menuItem != null) {
+ menuItem.setVisible(visible);
+ }
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
- getActivity().onBackPressed();
+ if (!hasNameChange() && !hasMembershipChange()) {
+ getActivity().onBackPressed();
+ } else {
+ CancelEditDialogFragment.show(this);
+ }
return true;
}
+ case R.id.menu_add:
+ if (mListener != null) {
+ mListener.setSearchMode(true);
+ }
+ return true;
case R.id.menu_save:
onDoneClicked();
return true;
@@ -586,7 +631,7 @@
// If there are no changes, then go straight to onSaveCompleted()
if (!hasNameChange() && !hasMembershipChange()) {
- onSaveCompleted(false, mGroupUri);
+ onSaveCompleted(false, mGroupMetadata.uri);
return true;
}
@@ -607,7 +652,7 @@
new AccountWithDataSet(mAccountName, mAccountType, mDataSet),
mGroupNameView.getText().toString(),
membersToAddArray, activity.getClass(),
- GroupEditorActivity.ACTION_SAVE_COMPLETED);
+ GroupMembersActivity.ACTION_SAVE_COMPLETED);
} else if (Intent.ACTION_EDIT.equals(mAction)) {
// Create array of raw contact IDs for contacts to add to the group
long[] membersToAddArray = convertToArray(mListMembersToAdd);
@@ -616,9 +661,10 @@
long[] membersToRemoveArray = convertToArray(mListMembersToRemove);
// Create the update intent (which includes the updated group name if necessary)
- saveIntent = ContactSaveService.createGroupUpdateIntent(activity, mGroupId,
+ saveIntent = ContactSaveService.createGroupUpdateIntent(activity,
+ mGroupMetadata.groupId,
getUpdatedName(), membersToAddArray, membersToRemoveArray,
- activity.getClass(), GroupEditorActivity.ACTION_SAVE_COMPLETED);
+ activity.getClass(), GroupMembersActivity.ACTION_SAVE_COMPLETED);
} else {
throw new IllegalStateException("Invalid intent action type " + mAction);
}
@@ -636,21 +682,8 @@
final Intent resultIntent;
final int resultCode;
if (success && groupUri != null) {
- final String requestAuthority = groupUri.getAuthority();
-
resultIntent = new Intent();
- if (LEGACY_CONTACTS_AUTHORITY.equals(requestAuthority)) {
- // Build legacy Uri when requested by caller
- final long groupId = ContentUris.parseId(groupUri);
- final Uri legacyContentUri = Uri.parse("content://contacts/groups");
- final Uri legacyUri = ContentUris.withAppendedId(
- legacyContentUri, groupId);
- resultIntent.setData(legacyUri);
- } else {
- // Otherwise pass back the given Uri
- resultIntent.setData(groupUri);
- }
-
+ resultIntent.setData(GroupUtil.maybeConvertToLegacyUri(groupUri));
resultCode = Activity.RESULT_OK;
} else {
resultCode = Activity.RESULT_CANCELED;
@@ -744,30 +777,6 @@
}
/**
- * The listener for the group metadata (i.e. group name, account type, and account name) loader.
- */
- private final LoaderManager.LoaderCallbacks<Cursor> mGroupMetaDataLoaderListener =
- new LoaderCallbacks<Cursor>() {
-
- @Override
- public CursorLoader onCreateLoader(int id, Bundle args) {
- return new GroupMetaDataLoader(mContext, mGroupUri);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- bindGroupMetaData(data);
-
- // Load existing members
- getLoaderManager().initLoader(LOADER_EXISTING_MEMBERS, null,
- mGroupMemberListLoaderListener);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {}
- };
-
- /**
* The loader listener for the list of existing group members.
*/
private final LoaderManager.LoaderCallbacks<Cursor> mGroupMemberListLoaderListener =
@@ -775,7 +784,8 @@
@Override
public CursorLoader onCreateLoader(int id, Bundle args) {
- return GroupMemberLoader.constructLoaderForGroupEditorQuery(mContext, mGroupId);
+ return GroupMemberLoader.constructLoaderForGroupEditorQuery(
+ mContext, mGroupMetadata.groupId);
}
@Override
diff --git a/src/com/android/contacts/group/GroupMembersListAdapter.java b/src/com/android/contacts/group/GroupMembersListAdapter.java
index 71e8f8d..167c014 100644
--- a/src/com/android/contacts/group/GroupMembersListAdapter.java
+++ b/src/com/android/contacts/group/GroupMembersListAdapter.java
@@ -28,17 +28,18 @@
import android.view.ViewGroup;
import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
-import com.android.contacts.common.list.ContactEntryListAdapter;
import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
import com.android.contacts.common.preference.ContactsPreferences;
/** Group members cursor adapter. */
-public class GroupMembersListAdapter extends ContactEntryListAdapter {
+public class GroupMembersListAdapter extends MultiSelectEntryContactListAdapter {
private static class GroupMembersQuery {
private static final String[] PROJECTION_PRIMARY = new String[] {
Data.CONTACT_ID,
+ Data.RAW_CONTACT_ID,
Data.PHOTO_ID,
Data.LOOKUP_KEY,
Data.CONTACT_PRESENCE,
@@ -48,6 +49,7 @@
private static final String[] PROJECTION_ALTERNATIVE = new String[] {
Data.CONTACT_ID,
+ Data.RAW_CONTACT_ID,
Data.PHOTO_ID,
Data.LOOKUP_KEY,
Data.CONTACT_PRESENCE,
@@ -56,18 +58,19 @@
};
public static final int CONTACT_ID = 0;
- public static final int CONTACT_PHOTO_ID = 1;
- public static final int CONTACT_LOOKUP_KEY = 2;
- public static final int CONTACT_PRESENCE = 3;
- public static final int CONTACT_STATUS = 4;
- public static final int CONTACT_DISPLAY_NAME = 5;
+ public static final int RAW_CONTACT_ID = 1;
+ public static final int CONTACT_PHOTO_ID = 2;
+ public static final int CONTACT_LOOKUP_KEY = 3;
+ public static final int CONTACT_PRESENCE = 4;
+ public static final int CONTACT_STATUS = 5;
+ public static final int CONTACT_DISPLAY_NAME = 6;
}
private final CharSequence mUnknownNameText;
private long mGroupId;
public GroupMembersListAdapter(Context context) {
- super(context);
+ super(context, GroupMembersQuery.RAW_CONTACT_ID);
mUnknownNameText = context.getText(android.R.string.unknownName);
setIndexedPartition(0);
}
@@ -116,6 +119,13 @@
return ((Cursor) getItem(position)).getString(GroupMembersQuery.CONTACT_DISPLAY_NAME);
}
+ public Uri getContactUri(int position) {
+ final Cursor cursor = (Cursor) getItem(position);
+ final long contactId = cursor.getLong(GroupMembersQuery.CONTACT_ID);
+ final String lookupKey = cursor.getString(GroupMembersQuery.CONTACT_LOOKUP_KEY);
+ return Contacts.getLookupUri(contactId, lookupKey);
+ }
+
@Override
protected ContactListItemView newView(Context context, int partition, Cursor cursor,
int position, ViewGroup parent) {
diff --git a/src/com/android/contacts/group/GroupMembersListFragment.java b/src/com/android/contacts/group/GroupMembersListFragment.java
index b8dad5e..b896a4e 100644
--- a/src/com/android/contacts/group/GroupMembersListFragment.java
+++ b/src/com/android/contacts/group/GroupMembersListFragment.java
@@ -15,207 +15,44 @@
*/
package com.android.contacts.group;
-import android.app.Activity;
-import android.app.LoaderManager.LoaderCallbacks;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.database.Cursor;
+import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.ContactsContract.Groups;
-import android.util.Log;
import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import com.android.contacts.GroupListLoader;
-import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
-import com.android.contacts.common.list.ContactEntryListFragment;
-import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.interactions.GroupDeletionDialogFragment;
+import com.android.contacts.list.MultiSelectContactsListFragment;
/** Displays the members of a group. */
-public class GroupMembersListFragment extends ContactEntryListFragment<GroupMembersListAdapter> {
+public class GroupMembersListFragment extends MultiSelectContactsListFragment {
- private static final String TAG = "GroupMembersList";
-
- private static final String KEY_GROUP_URI = "groupUri";
private static final String KEY_GROUP_METADATA = "groupMetadata";
- private static final int LOADER_GROUP_METADATA = 0;
- private static final int LOADER_GROUP_LIST_DETAILS = 1;
-
- private final LoaderCallbacks<Cursor> mGroupMetadataCallbacks = new LoaderCallbacks<Cursor>() {
-
- @Override
- public CursorLoader onCreateLoader(int id, Bundle args) {
- return new GroupMetaDataLoader(getContext(), mGroupUri);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (cursor == null || cursor.isClosed()) {
- Log.e(TAG, "Failed to load group metadata");
- return;
- }
- if (cursor.moveToNext()) {
- final boolean deleted = cursor.getInt(GroupMetaDataLoader.DELETED) == 1;
- if (!deleted) {
- mGroupMetadata = new GroupMetadata();
- mGroupMetadata.accountType = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
- mGroupMetadata.dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
- mGroupMetadata.groupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID);
- mGroupMetadata.groupName = cursor.getString(GroupMetaDataLoader.TITLE);
- mGroupMetadata.readOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1;
-
- final AccountTypeManager accountTypeManager =
- AccountTypeManager.getInstance(getContext());
- final AccountType accountType = accountTypeManager.getAccountType(
- mGroupMetadata.accountType, mGroupMetadata.dataSet);
- mGroupMetadata.editable = accountType.isGroupMembershipEditable();
-
- getLoaderManager().restartLoader(LOADER_GROUP_LIST_DETAILS, null,
- mGroupListDetailsCallbacks);
- }
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {}
- };
-
- private final LoaderCallbacks<Cursor> mGroupListDetailsCallbacks =
- new LoaderCallbacks<Cursor>() {
-
- @Override
- public CursorLoader onCreateLoader(int id, Bundle args) {
- final GroupListLoader groupListLoader = new GroupListLoader(getContext());
-
- // TODO(wjang): modify GroupListLoader to accept this selection criteria more naturally
- groupListLoader.setSelection(groupListLoader.getSelection()
- + " AND " + Groups._ID + "=?");
-
- final String[] selectionArgs = new String[1];
- selectionArgs[0] = Long.toString(mGroupMetadata.groupId);
- groupListLoader.setSelectionArgs(selectionArgs);
-
- return groupListLoader;
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (cursor == null || cursor.isClosed()) {
- Log.e(TAG, "Failed to load group list details");
- return;
- }
- if (cursor.moveToNext()) {
- mGroupMetadata.memberCount = cursor.getInt(GroupListLoader.MEMBER_COUNT);
- }
-
- onGroupMetadataLoaded();
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {}
- };
-
- private static final class GroupMetadata implements Parcelable {
-
- public static final Creator<GroupMetadata> CREATOR = new Creator<GroupMetadata>() {
-
- public GroupMetadata createFromParcel(Parcel in) {
- return new GroupMetadata(in);
- }
-
- public GroupMetadata[] newArray(int size) {
- return new GroupMetadata[size];
- }
- };
-
- String accountType;
- String dataSet;
- long groupId;
- String groupName;
- boolean readOnly;
- boolean editable;
- int memberCount = -1;
-
- GroupMetadata() {
- }
-
- GroupMetadata(Parcel source) {
- readFromParcel(source);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(accountType);
- dest.writeString(dataSet);
- dest.writeLong(groupId);
- dest.writeString(groupName);
- dest.writeInt(readOnly ? 1 : 0);
- dest.writeInt(editable ? 1 : 0);
- dest.writeInt(memberCount);
- }
-
- private void readFromParcel(Parcel source) {
- accountType = source.readString();
- dataSet = source.readString();
- groupId = source.readLong();
- groupName = source.readString();
- readOnly = source.readInt() == 1;
- editable = source.readInt() == 1;
- memberCount = source.readInt();
- }
-
- @Override
- public String toString() {
- return "GroupMetadata[accountType=" + accountType +
- " dataSet=" + dataSet +
- " groupId=" + groupId +
- " groupName=" + groupName +
- " readOnly=" + readOnly +
- " editable=" + editable +
- " memberCount=" + memberCount +
- "]";
- }
- }
+ private static final String ARG_GROUP_METADATA = "groupMetadata";
/** Callbacks for hosts of {@link GroupMembersListFragment}. */
- public interface GroupMembersListCallbacks {
-
- /** Invoked when the user hits back in the action bar. */
- void onHomePressed();
-
- /** Invoked after group metadata has been loaded. */
- void onGroupNameLoaded(String groupName);
+ public interface GroupMembersListListener {
/** Invoked when a group member in the list is clicked. */
- void onGroupMemberClicked(Uri contactLookupUri);
-
- /** Invoked when a user chooses ot edit the group whose members are being displayed. */
- void onEditGroup(Uri groupUri);
+ void onGroupMemberListItemClicked(Uri contactLookupUri);
}
- private Uri mGroupUri;
-
- private GroupMembersListCallbacks mCallbacks;
+ private GroupMembersListListener mListener;
private GroupMetadata mGroupMetadata;
+ public static GroupMembersListFragment newInstance(GroupMetadata groupMetadata) {
+ final Bundle args = new Bundle();
+ args.putParcelable(ARG_GROUP_METADATA, groupMetadata);
+
+ final GroupMembersListFragment fragment = new GroupMembersListFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
public GroupMembersListFragment() {
setHasOptionsMenu(true);
@@ -226,105 +63,53 @@
setQuickContactEnabled(false);
}
- /** Sets the Uri of the group whose members will be displayed. */
- public void setGroupUri(Uri groupUri) {
- mGroupUri = groupUri;
- }
-
- /** Sets a listener for group member click events. */
- public void setCallbacks(GroupMembersListCallbacks callbacks) {
- mCallbacks = callbacks;
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ try {
+ mListener = (GroupMembersListListener) getActivity();
+ } catch (ClassCastException e) {
+ throw new ClassCastException(getActivity() + " must implement " +
+ GroupMembersListListener.class.getSimpleName());
+ }
}
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
- if (savedState != null) {
- mGroupUri = savedState.getParcelable(KEY_GROUP_URI);
+ if (savedState == null) {
+ mGroupMetadata = getArguments().getParcelable(ARG_GROUP_METADATA);
+ } else {
mGroupMetadata = savedState.getParcelable(KEY_GROUP_METADATA);
}
+
+ // Don't attach the multi select check box listener if we can't edit the group
+ if (mGroupMetadata.editable) {
+ try {
+ setCheckBoxListListener((OnCheckBoxListActionListener) getActivity());
+ } catch (ClassCastException e) {
+ throw new ClassCastException(getActivity() + " must implement " +
+ OnCheckBoxListActionListener.class.getSimpleName());
+ }
+ }
+ }
+
+ @Override
+ public View onCreateView (LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View view = super.onCreateView(inflater, container, savedInstanceState);
+ bindMembersCount(view);
+ return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putParcelable(KEY_GROUP_URI, mGroupUri);
outState.putParcelable(KEY_GROUP_METADATA, mGroupMetadata);
}
- @Override
- protected void startLoading() {
- if (mGroupMetadata == null) {
- getLoaderManager().restartLoader(LOADER_GROUP_METADATA, null, mGroupMetadataCallbacks);
- } else {
- onGroupMetadataLoaded();
- }
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.view_group, menu);
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- final MenuItem editMenu = menu.findItem(R.id.menu_edit_group);
- editMenu.setVisible(isGroupEditable());
-
- final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group);
- deleteMenu.setVisible(isGroupDeletable());
- }
-
- private boolean isGroupEditable() {
- return mGroupUri != null && mGroupMetadata != null && mGroupMetadata.editable;
- }
-
- private boolean isGroupDeletable() {
- return mGroupUri != null && mGroupMetadata != null && !mGroupMetadata.readOnly;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home: {
- if (mCallbacks != null) {
- mCallbacks.onHomePressed();
- }
- return true;
- }
- case R.id.menu_edit_group: {
- if (mCallbacks != null) {
- mCallbacks.onEditGroup(mGroupUri);
- }
- break;
- }
- case R.id.menu_delete_group: {
- GroupDeletionDialogFragment.show(getFragmentManager(), mGroupMetadata.groupId,
- mGroupMetadata.groupName, /* endActivity */ true);
- return true;
- }
- }
- return false;
- }
-
- private void onGroupMetadataLoaded() {
- final Activity activity = getActivity();
- if (activity != null) activity.invalidateOptionsMenu();
-
- // Set the title
- if (mCallbacks != null) {
- mCallbacks.onGroupNameLoaded(mGroupMetadata.groupName);
- }
-
- // Set the header
- bindMembersCount();
-
- // Start loading the group members
- super.startLoading();
- }
-
- private void bindMembersCount() {
- final View accountFilterContainer = getView().findViewById(
+ private void bindMembersCount(View view) {
+ final View accountFilterContainer = view.findViewById(
R.id.account_filter_header_container);
if (mGroupMetadata.memberCount >= 0) {
accountFilterContainer.setVisibility(View.VISIBLE);
@@ -348,11 +133,14 @@
}
@Override
+ public GroupMembersListAdapter getAdapter() {
+ return (GroupMembersListAdapter) super.getAdapter();
+ }
+
+ @Override
protected void configureAdapter() {
super.configureAdapter();
- if (mGroupMetadata != null) {
- getAdapter().setGroupId(mGroupMetadata.groupId);
- }
+ getAdapter().setGroupId(mGroupMetadata.groupId);
}
@Override
@@ -362,9 +150,17 @@
@Override
protected void onItemClick(int position, long id) {
- if (mCallbacks != null) {
+ final Uri uri = getAdapter().getContactUri(position);
+ if (uri == null) {
+ return;
+ }
+ if (getAdapter().isDisplayingCheckBoxes()) {
+ super.onItemClick(position, id);
+ return;
+ }
+ if (mListener != null) {
final Uri contactLookupUri = getAdapter().getContactLookupUri(position);
- mCallbacks.onGroupMemberClicked(contactLookupUri);
+ mListener.onGroupMemberListItemClicked(contactLookupUri);
}
}
}
diff --git a/src/com/android/contacts/group/GroupMetadata.java b/src/com/android/contacts/group/GroupMetadata.java
new file mode 100644
index 0000000..788c1d4
--- /dev/null
+++ b/src/com/android/contacts/group/GroupMetadata.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 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.group;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** Meta data for a contact group. */
+// TODO(wjang): consolidate with com.android.contacts.common.GroupMetaData;
+public final class GroupMetadata implements Parcelable {
+
+ public static final Creator<GroupMetadata> CREATOR = new Creator<GroupMetadata>() {
+
+ public GroupMetadata createFromParcel(Parcel in) {
+ return new GroupMetadata(in);
+ }
+
+ public GroupMetadata[] newArray(int size) {
+ return new GroupMetadata[size];
+ }
+ };
+
+ // TODO(wjang): make them all final and add getters
+ public Uri uri;
+ public String accountName;
+ public String accountType;
+ public String dataSet;
+ public long groupId;
+ public String groupName;
+ public boolean readOnly;
+ public boolean editable;
+ public int memberCount = -1;
+
+ public GroupMetadata() {
+ }
+
+ public GroupMetadata(Parcel source) {
+ readFromParcel(source);
+ }
+
+ private void readFromParcel(Parcel source) {
+ uri = source.readParcelable(Uri.class.getClassLoader());
+ accountName = source.readString();
+ accountType = source.readString();
+ dataSet = source.readString();
+ groupId = source.readLong();
+ groupName = source.readString();
+ readOnly = source.readInt() == 1;
+ editable = source.readInt() == 1;
+ memberCount = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(uri, 0);
+ dest.writeString(accountName);
+ dest.writeString(accountType);
+ dest.writeString(dataSet);
+ dest.writeLong(groupId);
+ dest.writeString(groupName);
+ dest.writeInt(readOnly ? 1 : 0);
+ dest.writeInt(editable ? 1 : 0);
+ dest.writeInt(memberCount);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "GroupMetadata[uri=" + uri +
+ " accountName=" + accountName +
+ " accountType=" + accountType +
+ " dataSet=" + dataSet +
+ " groupId=" + groupId +
+ " groupName=" + groupName +
+ " readOnly=" + readOnly +
+ " editable=" + editable +
+ " memberCount=" + memberCount +
+ "]";
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/group/GroupUtil.java b/src/com/android/contacts/group/GroupUtil.java
index 6d6fec1..f9f5007 100644
--- a/src/com/android/contacts/group/GroupUtil.java
+++ b/src/com/android/contacts/group/GroupUtil.java
@@ -24,7 +24,6 @@
import android.provider.ContactsContract.Groups;
import com.android.contacts.GroupListLoader;
-import com.android.contacts.activities.GroupEditorActivity;
import com.android.contacts.activities.GroupMembersActivity;
import com.google.common.base.Objects;
@@ -33,6 +32,9 @@
*/
public final class GroupUtil {
+ private static final String LEGACY_CONTACTS_AUTHORITY = "contacts";
+ private static final String LEGACY_CONTACTS_URI = "content://contacts/groups";
+
private GroupUtil() {
}
@@ -71,18 +73,40 @@
/** Returns an Intent to create a new group. */
public static Intent createAddGroupIntent(Context context) {
- final Intent intent = new Intent(context, GroupEditorActivity.class);
+ final Intent intent = new Intent(context, GroupMembersActivity.class);
intent.setAction(Intent.ACTION_INSERT);
return intent;
}
- /** Returns an Intent to view the details of the group identified by the given Uri. */
+ /** Returns an Intent to view the details of the group identified by the given ID. */
public static Intent createViewGroupIntent(Context context, long groupId) {
+ return createViewGroupIntent(context, getGroupUriFromId(groupId));
+ }
+
+ /** Returns an Intent to view the details of the group identified by the given Uri. */
+ public static Intent createViewGroupIntent(Context context, Uri uri) {
final Intent intent = new Intent(context, GroupMembersActivity.class);
- intent.setData(getGroupUriFromId(groupId));
+ intent.setAction(Intent.ACTION_VIEW);
+ // TODO(wjang): do we still need it?
+ // intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.setData(uri);
return intent;
}
+ /**
+ * Converts the given group Uri to the legacy format if the legacy authority was specified
+ * in the given Uri.
+ */
+ public static Uri maybeConvertToLegacyUri(Uri groupUri) {
+ final String requestAuthority = groupUri.getAuthority();
+ if (!LEGACY_CONTACTS_AUTHORITY.equals(requestAuthority)) {
+ return groupUri;
+ }
+ final long groupId = ContentUris.parseId(groupUri);
+ final Uri legacyContentUri = Uri.parse(LEGACY_CONTACTS_URI);
+ return ContentUris.withAppendedId(legacyContentUri, groupId);
+ }
+
/** TODO: Make it private after {@link GroupBrowseListAdapter} is removed. */
static Uri getGroupUriFromId(long groupId) {
return ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 4b4b326..ea55333 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -36,7 +36,6 @@
import com.android.common.widget.CompositeCursorAdapter.Partition;
import com.android.contacts.common.list.AutoScrollListView;
-import com.android.contacts.common.list.ContactEntryListFragment;
import com.android.contacts.common.list.ContactListAdapter;
import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.common.list.DirectoryPartition;
@@ -49,7 +48,7 @@
* picking a contact with one of the PICK intents).
*/
public abstract class ContactBrowseListFragment extends
- ContactEntryListFragment<ContactListAdapter> {
+ MultiSelectContactsListFragment<ContactListAdapter> {
private static final String TAG = "ContactList";
@@ -383,6 +382,11 @@
}
@Override
+ public ContactListAdapter getAdapter() {
+ return (ContactListAdapter) super.getAdapter();
+ }
+
+ @Override
protected void configureAdapter() {
super.configureAdapter();
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 672e63e..97bb86a 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -39,8 +39,6 @@
* picking a contact with one of the PICK intents).
*/
public class DefaultContactBrowseListFragment extends ContactBrowseListFragment {
- private static final String TAG = DefaultContactBrowseListFragment.class.getSimpleName();
-
private View mSearchHeaderView;
private View mSearchProgress;
private TextView mSearchProgressText;
@@ -65,6 +63,10 @@
if (uri == null) {
return;
}
+ if (getAdapter().isDisplayingCheckBoxes()) {
+ super.onItemClick(position, id);
+ return;
+ }
viewContact(uri, getAdapter().isEnterpriseContact(position));
}
@@ -147,4 +149,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/MultiSelectContactsListFragment.java b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
index 8dd18c1..53f5a74 100644
--- a/src/com/android/contacts/list/MultiSelectContactsListFragment.java
+++ b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
@@ -16,18 +16,16 @@
package com.android.contacts.list;
-import com.android.contacts.common.list.ContactListAdapter;
-import com.android.contacts.common.list.ContactListItemView;
-import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.list.ContactEntryListFragment;
+import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
+import com.android.contacts.common.list.MultiSelectEntryContactListAdapter.SelectedContactsListener;
import com.android.contacts.common.logging.SearchState;
-import com.android.contacts.list.MultiSelectEntryContactListAdapter.SelectedContactsListener;
import com.android.contacts.common.logging.Logger;
import android.database.Cursor;
-import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
-import android.text.TextUtils;
+import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import java.util.ArrayList;
@@ -38,9 +36,12 @@
* Fragment containing a contact list used for browsing contacts and optionally selecting
* multiple contacts via checkboxes.
*/
-public class MultiSelectContactsListFragment extends DefaultContactBrowseListFragment
+public abstract class MultiSelectContactsListFragment<T extends MultiSelectEntryContactListAdapter>
+ extends ContactEntryListFragment<T>
implements SelectedContactsListener {
+ private static final String TAG = "MultiContactsList";
+
public interface OnCheckBoxListActionListener {
void onStartDisplayingCheckBoxes();
void onSelectedContactIdsChanged();
@@ -60,7 +61,7 @@
/**
* Whether a search result was clicked by the user. Tracked so that we can distinguish
- * between exiting the search mode after a result was clicked from existing w/o clicking
+ * between exiting the search mode after a result was clicked from exiting w/o clicking
* any search result.
*/
public boolean wasSearchResultClicked() {
@@ -76,9 +77,7 @@
@Override
public void onSelectedContactsChanged() {
- if (mCheckBoxListListener != null) {
- mCheckBoxListListener.onSelectedContactIdsChanged();
- }
+ if (mCheckBoxListListener != null) mCheckBoxListListener.onSelectedContactIdsChanged();
}
@Override
@@ -106,13 +105,11 @@
}
public TreeSet<Long> getSelectedContactIds() {
- final MultiSelectEntryContactListAdapter adapter = getAdapter();
- return adapter.getSelectedContactIds();
+ return getAdapter().getSelectedContactIds();
}
- @Override
- public MultiSelectEntryContactListAdapter getAdapter() {
- return (MultiSelectEntryContactListAdapter) super.getAdapter();
+ public long[] getSelectedContactIdsArray() {
+ return getAdapter().getSelectedContactIdsArray();
}
@Override
@@ -129,9 +126,11 @@
}
public void displayCheckBoxes(boolean displayCheckBoxes) {
- getAdapter().setDisplayCheckBoxes(displayCheckBoxes);
- if (!displayCheckBoxes) {
- clearCheckBoxes();
+ if (getAdapter() != null) {
+ getAdapter().setDisplayCheckBoxes(displayCheckBoxes);
+ if (!displayCheckBoxes) {
+ clearCheckBoxes();
+ }
}
}
@@ -142,23 +141,20 @@
@Override
protected boolean onItemLongClick(int position, long id) {
final int previouslySelectedCount = getAdapter().getSelectedContactIds().size();
- final Uri uri = getAdapter().getContactUri(position);
+ final long contactId = getContactId(position);
final int partition = getAdapter().getPartitionForPosition(position);
- if (uri != null && partition == ContactsContract.Directory.DEFAULT) {
- final String contactId = uri.getLastPathSegment();
- if (!TextUtils.isEmpty(contactId)) {
- if (mCheckBoxListListener != null) {
- mCheckBoxListListener.onStartDisplayingCheckBoxes();
- }
- getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId));
- // Manually send clicked event if there is a checkbox.
- // See b/24098561. TalkBack will not read it otherwise.
- final int index = position + getListView().getHeaderViewsCount() - getListView()
- .getFirstVisiblePosition();
- if (index >= 0 && index < getListView().getChildCount()) {
- getListView().getChildAt(index).sendAccessibilityEvent(AccessibilityEvent
- .TYPE_VIEW_CLICKED);
- }
+ if (contactId >= 0 && partition == ContactsContract.Directory.DEFAULT) {
+ if (mCheckBoxListListener != null) {
+ mCheckBoxListListener.onStartDisplayingCheckBoxes();
+ }
+ getAdapter().toggleSelectionOfContactId(contactId);
+ // Manually send clicked event if there is a checkbox.
+ // See b/24098561. TalkBack will not read it otherwise.
+ final int index = position + getListView().getHeaderViewsCount() - getListView()
+ .getFirstVisiblePosition();
+ if (index >= 0 && index < getListView().getChildCount()) {
+ getListView().getChildAt(index).sendAccessibilityEvent(AccessibilityEvent
+ .TYPE_VIEW_CLICKED);
}
}
final int nowSelectedCount = getAdapter().getSelectedContactIds().size();
@@ -172,27 +168,37 @@
@Override
protected void onItemClick(int position, long id) {
- final Uri uri = getAdapter().getContactUri(position);
- if (uri == null) {
+ final long contactId = getContactId(position);
+ if (contactId < 0) {
return;
}
if (getAdapter().isDisplayingCheckBoxes()) {
- final String contactId = uri.getLastPathSegment();
- if (!TextUtils.isEmpty(contactId)) {
- getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId));
- }
+ getAdapter().toggleSelectionOfContactId(contactId);
} else {
if (isSearchMode()) {
mSearchResultClicked = true;
Logger.logSearchEvent(createSearchStateForSearchResultClick(position));
}
- super.onItemClick(position, id);
}
if (mCheckBoxListListener != null && getAdapter().getSelectedContactIds().size() == 0) {
mCheckBoxListListener.onStopDisplayingCheckBoxes();
}
}
+ private long getContactId(int position) {
+ final int contactIdColumnIndex = getAdapter().getContactColumnIdIndex();
+
+ final Cursor cursor = (Cursor) getAdapter().getItem(position);
+ if (cursor != null) {
+ if (cursor.getColumnCount() > contactIdColumnIndex) {
+ return cursor.getLong(contactIdColumnIndex);
+ }
+ }
+
+ Log.w(TAG, "Failed to get contact ID from cursor column " + contactIdColumnIndex);
+ return -1;
+ }
+
/**
* Returns the state of the search results currently presented to the user.
*/
@@ -259,14 +265,4 @@
}
return searchState;
}
-
- @Override
- protected ContactListAdapter createListAdapter() {
- DefaultContactListAdapter adapter = new MultiSelectEntryContactListAdapter(getContext());
- adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
- adapter.setDisplayPhotos(true);
- adapter.setPhotoPosition(
- ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false));
- return adapter;
- }
}
diff --git a/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java b/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java
deleted file mode 100644
index 5a54c51..0000000
--- a/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2015 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.list;
-
-import com.android.contacts.common.list.ContactListItemView;
-import com.android.contacts.common.list.DefaultContactListAdapter;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.CheckBox;
-
-import java.util.TreeSet;
-
-/**
- * An extension of the default contact adapter that adds checkboxes and the ability
- * to select multiple contacts.
- */
-public class MultiSelectEntryContactListAdapter extends DefaultContactListAdapter {
-
- private SelectedContactsListener mSelectedContactsListener;
- private TreeSet<Long> mSelectedContactIds = new TreeSet<Long>();
- private boolean mDisplayCheckBoxes;
-
- public interface SelectedContactsListener {
- void onSelectedContactsChanged();
- void onSelectedContactsChangedViaCheckBox();
- }
-
- public MultiSelectEntryContactListAdapter(Context context) {
- super(context);
- }
-
- public void setSelectedContactsListener(SelectedContactsListener listener) {
- mSelectedContactsListener = listener;
- }
-
- /**
- * Returns set of selected contacts.
- */
- public TreeSet<Long> getSelectedContactIds() {
- return mSelectedContactIds;
- }
-
- /**
- * Update set of selected contacts. This changes which checkboxes are set.
- */
- public void setSelectedContactIds(TreeSet<Long> selectedContactIds) {
- this.mSelectedContactIds = selectedContactIds;
- notifyDataSetChanged();
- if (mSelectedContactsListener != null) {
- mSelectedContactsListener.onSelectedContactsChanged();
- }
- }
-
- /**
- * Shows checkboxes beside contacts if {@param displayCheckBoxes} is {@code TRUE}.
- * Not guaranteed to work with all configurations of this adapter.
- */
- public void setDisplayCheckBoxes(boolean showCheckBoxes) {
- if (!mDisplayCheckBoxes && showCheckBoxes) {
- setSelectedContactIds(new TreeSet<Long>());
- }
- mDisplayCheckBoxes = showCheckBoxes;
- notifyDataSetChanged();
- if (mSelectedContactsListener != null) {
- mSelectedContactsListener.onSelectedContactsChanged();
- }
- }
-
- /**
- * Checkboxes are being displayed beside contacts.
- */
- public boolean isDisplayingCheckBoxes() {
- return mDisplayCheckBoxes;
- }
-
- /**
- * Toggle the checkbox beside the contact for {@param contactId}.
- */
- public void toggleSelectionOfContactId(long contactId) {
- if (mSelectedContactIds.contains(contactId)) {
- mSelectedContactIds.remove(contactId);
- } else {
- mSelectedContactIds.add(contactId);
- }
- notifyDataSetChanged();
- if (mSelectedContactsListener != null) {
- mSelectedContactsListener.onSelectedContactsChanged();
- }
- }
-
- @Override
- protected void bindView(View itemView, int partition, Cursor cursor, int position) {
- super.bindView(itemView, partition, cursor, position);
- final ContactListItemView view = (ContactListItemView) itemView;
- bindCheckBox(view, cursor, position, partition == ContactsContract.Directory.DEFAULT);
- }
-
- private void bindCheckBox(ContactListItemView view, Cursor cursor, int position,
- boolean isLocalDirectory) {
- // Disable clicking on all contacts from remote directories when showing check boxes. We do
- // this by telling the view to handle clicking itself.
- view.setClickable(!isLocalDirectory && mDisplayCheckBoxes);
- // Only show checkboxes if mDisplayCheckBoxes is enabled. Also, never show the
- // checkbox for other directory contacts except local directory.
- if (!mDisplayCheckBoxes || !isLocalDirectory) {
- view.hideCheckBox();
- return;
- }
- final CheckBox checkBox = view.getCheckBox();
- final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
- checkBox.setChecked(mSelectedContactIds.contains(contactId));
- checkBox.setTag(contactId);
- checkBox.setOnClickListener(mCheckBoxClickListener);
- }
-
- private final OnClickListener mCheckBoxClickListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- final CheckBox checkBox = (CheckBox) v;
- final Long contactId = (Long) checkBox.getTag();
- if (checkBox.isChecked()) {
- mSelectedContactIds.add(contactId);
- } else {
- mSelectedContactIds.remove(contactId);
- }
- notifyDataSetChanged();
- if (mSelectedContactsListener != null) {
- mSelectedContactsListener.onSelectedContactsChangedViaCheckBox();
- }
- }
- };
-}
diff --git a/src/com/android/contacts/widget/NoSwipeViewPager.java b/src/com/android/contacts/widget/NoSwipeViewPager.java
new file mode 100644
index 0000000..b24df39
--- /dev/null
+++ b/src/com/android/contacts/widget/NoSwipeViewPager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 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.widget;
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+/**
+ * ViewPager with swipe disabled.
+ */
+public class NoSwipeViewPager extends ViewPager {
+
+ public NoSwipeViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return false;
+ }
+}
\ No newline at end of file