Merge "Align list item with hamburger menu and activity title (2/2)"
diff --git a/res/drawable/ic_history_24dp.xml b/res/drawable/ic_history_24dp.xml
new file mode 100644
index 0000000..1db190e
--- /dev/null
+++ b/res/drawable/ic_history_24dp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- History icon -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/group_members_activity.xml b/res/layout/group_members_activity.xml
index eecec50..387fdd2 100644
--- a/res/layout/group_members_activity.xml
+++ b/res/layout/group_members_activity.xml
@@ -25,10 +25,4 @@
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/layout/create_group_dialog.xml b/res/layout/group_name_edit_dialog.xml
similarity index 95%
rename from res/layout/create_group_dialog.xml
rename to res/layout/group_name_edit_dialog.xml
index 3fefd3d..2858388 100644
--- a/res/layout/create_group_dialog.xml
+++ b/res/layout/group_name_edit_dialog.xml
@@ -30,6 +30,6 @@
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="16dp"
- android:inputType="textCapWords"
+ android:inputType="textCapWords|textNoSuggestions"
android:maxLength="40"/>
</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/quickcontact_content.xml b/res/layout/quickcontact_content.xml
index a6a8abb..b6cff4b 100644
--- a/res/layout/quickcontact_content.xml
+++ b/res/layout/quickcontact_content.xml
@@ -50,6 +50,12 @@
<com.android.contacts.quickcontact.ExpandingEntryCardView
style="@style/ExpandingEntryCardStyle"
+ android:id="@+id/permission_explanation_card"
+ android:visibility="gone"
+ cardview:cardCornerRadius="@dimen/expanding_entry_card_card_corner_radius"/>
+
+ <com.android.contacts.quickcontact.ExpandingEntryCardView
+ style="@style/ExpandingEntryCardStyle"
android:id="@+id/about_card"
android:visibility="gone"
cardview:cardCornerRadius="@dimen/expanding_entry_card_card_corner_radius" />
diff --git a/res/menu/view_group.xml b/res/menu/view_group.xml
index 6c5979e..a937598 100644
--- a/res/menu/view_group.xml
+++ b/res/menu/view_group.xml
@@ -18,6 +18,12 @@
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_edit_group"
android:icon="@drawable/ic_create_24dp"
android:title="@string/menu_editGroup"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 22169e4..8e7fdfd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -320,9 +320,24 @@
<!-- Toast displayed when saving a contact photo failed. [CHAR LIMIT=NONE] -->
<string name="contactPhotoSavedErrorToast">Couldn\'t save contact photo changes.</string>
+ <!-- Toast displayed when something goes wrong while loading a label. [CHAR LIMIT=70] -->
+ <string name="groupLoadErrorToast">Failed to load label</string>
+
<!-- Toast displayed when a label is saved [CHAR LIMIT=30] -->
<string name="groupSavedToast">Label saved</string>
+ <!-- Toast displayed when a label name is updated. [CHAR LIMIT=50] -->
+ <string name="groupCreatedToast">Label created</string>
+
+ <!-- Toast displayed when a new label is created. [CHAR LIMIT=50] -->
+ <string name="groupUpdatedToast">Label updated</string>
+
+ <!-- Toast displayed when contacts are removed from a label. [CHAR LIMIT=50] -->
+ <string name="groupMembersRemovedToast">Removed contacts</string>
+
+ <!-- Toast displayed when a contact is added to a label. [CHAR LIMIT=50] -->
+ <string name="groupMembersAddedToast">Added contact</string>
+
<!-- Toast displayed when saving a label failed [CHAR LIMIT=70] -->
<string name="groupSavedErrorToast">Couldn\'t save label changes.</string>
@@ -414,10 +429,10 @@
<string name="dialog_new_group_account">Choose account</string>
<!-- Title for the create new label dialog. CHAR LIMIT=40] -->
- <string name="create_group_dialog_title">Create label</string>
+ <string name="insert_group_dialog_title">Create label</string>
- <!-- Button label to create a new label on the create new label dialog. [CHAR LIMIT=20] -->
- <string name="create_group_dialog_button">Create</string>
+ <!-- Title for the update label dialog. CHAR LIMIT=40] -->
+ <string name="update_group_dialog_title">Update label</string>
<!-- Generic action string for starting an audio chat. Used by AccessibilityService to announce the purpose of the view. [CHAR LIMIT=NONE] -->
<string name="audio_chat">Voice chat</string>
@@ -908,4 +923,20 @@
<!-- Menu section title of "accounts" [CHAR LIMIT=20] -->
<string name="menu_title_filters">Accounts</string>
-</resources>
+
+ <!-- Contacts app asking for permissions in QuickContact activity,
+ in order to display calendar and SMS history [CHAR LIMIT=60] -->
+ <string name="permission_explanation_header">See your history together</string>
+
+ <!-- Content displayed in QuickContact activity after Contacts app receiving
+ Calendar and SMS permissions [CHAR LIMIT=60] -->
+ <string name="permission_explanation_subheader_calendar_and_SMS">Events and Messages</string>
+
+ <!-- Content displayed in QuickContact activity after Contacts app receiving
+ Calendar permission [CHAR LIMIT=40] -->
+ <string name="permission_explanation_subheader_calendar">Events</string>
+
+ <!-- Content displayed in QuickContact activity after Contacts app receiving
+ SMS permission [CHAR LIMIT=40] -->
+ <string name="permission_explanation_subheader_SMS">Messages</string>
+</resources>
\ No newline at end of file
diff --git a/src/com/android/contacts/activities/GroupMembersActivity.java b/src/com/android/contacts/activities/GroupMembersActivity.java
index 7ebb3e0..9b9e8b0 100644
--- a/src/com/android/contacts/activities/GroupMembersActivity.java
+++ b/src/com/android/contacts/activities/GroupMembersActivity.java
@@ -15,7 +15,7 @@
*/
package com.android.contacts.activities;
-import android.app.Fragment;
+import android.accounts.Account;
import android.app.FragmentManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
@@ -24,33 +24,44 @@
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.provider.ContactsContract.Intents;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
import com.android.contacts.AppCompatContactsActivity;
import com.android.contacts.ContactSaveService;
-import com.android.contacts.GroupListLoader;
-import com.android.contacts.GroupMetaDataLoader;
+import com.android.contacts.GroupMemberLoader;
+import com.android.contacts.GroupMemberLoader.GroupEditorQuery;
import com.android.contacts.R;
+import com.android.contacts.common.editor.SelectAccountDialogFragment;
+import com.android.contacts.common.logging.Logger;
+import com.android.contacts.common.logging.ListEvent;
+import com.android.contacts.common.logging.ScreenEvent.ScreenType;
import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
import com.android.contacts.common.util.ImplicitIntentsUtil;
-import com.android.contacts.group.GroupEditorFragment;
import com.android.contacts.group.GroupMembersListFragment;
import com.android.contacts.group.GroupMetadata;
-import com.android.contacts.group.GroupUtil;
+import com.android.contacts.group.GroupNameEditDialogFragment;
+import com.android.contacts.group.Member;
+import com.android.contacts.group.SuggestedMemberListAdapter;
+import com.android.contacts.group.SuggestedMemberListAdapter.SuggestedMember;
import com.android.contacts.interactions.GroupDeletionDialogFragment;
import com.android.contacts.list.ContactsRequest;
import com.android.contacts.list.MultiSelectContactsListFragment;
import com.android.contacts.quickcontact.QuickContactActivity;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Displays the members of a group and allows the user to edit it.
*/
@@ -58,166 +69,51 @@
public class GroupMembersActivity extends AppCompatContactsActivity implements
ActionBarAdapter.Listener,
MultiSelectContactsListFragment.OnCheckBoxListActionListener,
+ SelectAccountDialogFragment.Listener,
GroupMembersListFragment.GroupMembersListListener,
- GroupEditorFragment.Listener {
+ GroupNameEditDialogFragment.Listener {
- private static final String TAG = "GroupMembersActivity";
+ private static final String TAG = "GroupMembers";
- private static final boolean DEBUG = false;
-
+ private static final String KEY_IS_INSERT_ACTION = "isInsertAction";
+ 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 static final String TAG_GROUP_MEMBERS = "groupMembers";
+ private static final String TAG_SELECT_ACCOUNT_DIALOG = "selectAccountDialog";
+ private static final String TAG_GROUP_NAME_EDIT_DIALOG = "groupNameEditDialog";
- private static final int FRAGMENT_MEMBERS_LIST = -1;
- private static final int FRAGMENT_EDITOR = -2;
+ private static final int LOADER_GROUP_MEMBERS = 0;
- public static final String ACTION_SAVE_COMPLETED = "saveCompleted";
+ private static final String ACTION_CREATE_GROUP = "createGroup";
+ private static final String ACTION_UPDATE_GROUP = "updateGroup";
+ private static final String ACTION_ADD_TO_GROUP = "addToGroup";
+ private static final String ACTION_REMOVE_FROM_GROUP = "removeFromGroup";
- 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>() {
+ /** Loader callbacks for existing group members for the autocomplete text view. */
+ private final LoaderCallbacks<Cursor> mGroupMemberCallbacks = new LoaderCallbacks<Cursor>() {
@Override
public CursorLoader onCreateLoader(int id, Bundle args) {
- return new GroupMetaDataLoader(GroupMembersActivity.this, mGroupUri);
+ return GroupMemberLoader.constructLoaderForGroupEditorQuery(
+ GroupMembersActivity.this, mGroupMetadata.groupId);
}
@Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (cursor == null || cursor.isClosed()) {
- Log.e(TAG, "Failed to load group metadata");
- return;
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ final List<Member> members = new ArrayList<>();
+ data.moveToPosition(-1);
+ while (data.moveToNext()) {
+ members.add(new Member(
+ data.getLong(GroupEditorQuery.RAW_CONTACT_ID),
+ data.getString(GroupEditorQuery.CONTACT_LOOKUP_KEY),
+ data.getLong(GroupEditorQuery.CONTACT_ID),
+ data.getString(GroupEditorQuery.CONTACT_DISPLAY_NAME_PRIMARY),
+ data.getString(GroupEditorQuery.CONTACT_PHOTO_URI),
+ data.getLong(GroupEditorQuery.CONTACT_PHOTO_ID)));
}
- 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();
+ bindAutocompleteGroupMembers(members);
}
@Override
@@ -225,93 +121,173 @@
};
private ActionBarAdapter mActionBarAdapter;
- private ViewPager mViewPager;
- private GroupPagerAdapter mPagerAdapter;
-
- private Uri mGroupUri;
private GroupMetadata mGroupMetadata;
private GroupMembersListFragment mMembersListFragment;
- private GroupEditorFragment mEditorFragment;
+ private SuggestedMemberListAdapter mAutoCompleteAdapter;
+
+ private Uri mGroupUri;
private boolean mIsInsertAction;
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
- mIsInsertAction = Intent.ACTION_INSERT.equals(getIntent().getAction());
-
- mGroupUri = getIntent().getData();
+ // Parse the Intent
if (savedState != null) {
+ mGroupUri = savedState.getParcelable(KEY_GROUP_URI);
+ mIsInsertAction = savedState.getBoolean(KEY_IS_INSERT_ACTION);
mGroupMetadata = savedState.getParcelable(KEY_GROUP_METADATA);
+ } else {
+ mGroupUri = getIntent().getData();
+ mIsInsertAction = Intent.ACTION_INSERT.equals(getIntent().getAction());
+ }
+ if (!mIsInsertAction && mGroupUri == null) {
+ setResultCanceledAndFinish(R.string.groupLoadErrorToast);
+ return;
}
- // Setup the view
+ // Set up the view
setContentView(R.layout.group_members_activity);
- mViewPager = (ViewPager) findViewById(R.id.view_pager);
// 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);
+
+ // Decide whether to prompt for the account and group name or start loading existing members
+ if (mIsInsertAction) {
+ // Check if we are in the middle of the insert flow.
+ if (!isSelectAccountDialogFound() && !isGroupNameEditDialogFound()) {
+
+ // Create metadata to hold the account info
+ mGroupMetadata = new GroupMetadata();
+
+ // Select the account to create the group
+ final Bundle extras = getIntent().getExtras();
+ final Account account = extras == null ? null :
+ (Account) extras.getParcelable(Intents.Insert.EXTRA_ACCOUNT);
+ if (account == null) {
+ selectAccount();
+ } else {
+ final String dataSet = extras == null
+ ? null : extras.getString(Intents.Insert.EXTRA_DATA_SET);
+ final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
+ account.name, account.type, dataSet);
+ onAccountChosen(accountWithDataSet, /* extraArgs */ null);
+ }
+ }
+ } else {
+ // Add the members list fragment
+ final FragmentManager fragmentManager = getFragmentManager();
+ mMembersListFragment = (GroupMembersListFragment)
+ fragmentManager.findFragmentByTag(TAG_GROUP_MEMBERS);
+ if (mMembersListFragment == null) {
+ mMembersListFragment = GroupMembersListFragment.newInstance(getIntent().getData());
+ fragmentManager.beginTransaction()
+ .replace(R.id.fragment_container, mMembersListFragment, TAG_GROUP_MEMBERS)
+ .commit();
+ } else {
+ getLoaderManager().initLoader(LOADER_GROUP_MEMBERS, null, mGroupMemberCallbacks);
+ }
+ mMembersListFragment.setListener(this);
+ if (mGroupMetadata != null && mGroupMetadata.editable) {
+ mMembersListFragment.setCheckBoxListListener(this);
+ }
+ }
+
+ // Delay action bar initialization until after the fragment is added
+ final ContactsRequest contactsRequest = new ContactsRequest();
+ contactsRequest.setActionCode(ContactsRequest.ACTION_GROUP);
mActionBarAdapter.initialize(savedState, contactsRequest);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
+ if (mActionBarAdapter != null) {
+ mActionBarAdapter.onSaveInstanceState(outState);
+ }
+ outState.putBoolean(KEY_IS_INSERT_ACTION, mIsInsertAction);
+ outState.putParcelable(KEY_GROUP_URI, mGroupUri);
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();
- }
+ private void selectAccount() {
+ final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(this)
+ .getAccounts(/* writable */ true);
+ if (accounts.isEmpty()) {
+ setResultCanceledAndFinish();
+ return;
}
+ // If there is a single writable account, use it w/o showing a dialog.
+ if (accounts.size() == 1) {
+ onAccountChosen(accounts.get(0), /* extraArgs */ null);
+ return;
+ }
+ SelectAccountDialogFragment.show(getFragmentManager(), null,
+ R.string.dialog_new_group_account, AccountListFilter.ACCOUNTS_GROUP_WRITABLE,
+ /* extraArgs */ null, TAG_SELECT_ACCOUNT_DIALOG);
}
+ // Invoked with results from the ContactSaveService
@Override
protected void onNewIntent(Intent newIntent) {
super.onNewIntent(newIntent);
- if (ACTION_SAVE_COMPLETED.equals(newIntent.getAction())) {
+ if (isSaveAction(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);
+ setResultCanceledAndFinish();
+ return;
}
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + groupUri);
+
+ mGroupUri = groupUri;
+ mIsInsertAction = false;
+
+ Toast.makeText(this, getToastMessageForSaveAction(newIntent.getAction()),
+ Toast.LENGTH_SHORT).show();
+
+ mMembersListFragment = GroupMembersListFragment.newInstance(groupUri);
+ mMembersListFragment.setListener(this);
+ getFragmentManager().beginTransaction()
+ .replace(R.id.fragment_container, mMembersListFragment, TAG_GROUP_MEMBERS)
+ .commit();
+ if (mGroupMetadata != null && mGroupMetadata.editable) {
+ mMembersListFragment.setCheckBoxListListener(this);
+ }
+
+ invalidateOptionsMenu();
}
}
+ private static boolean isSaveAction(String action) {
+ return ACTION_CREATE_GROUP.equals(action)
+ || ACTION_UPDATE_GROUP.equals(action)
+ || ACTION_ADD_TO_GROUP.equals(action)
+ || ACTION_REMOVE_FROM_GROUP.equals(action);
+ }
+
+ private static int getToastMessageForSaveAction(String action) {
+ if (ACTION_CREATE_GROUP.equals(action)) return R.string.groupCreatedToast;
+ if (ACTION_UPDATE_GROUP.equals(action)) return R.string.groupUpdatedToast;
+ if (ACTION_ADD_TO_GROUP.equals(action)) return R.string.groupMembersAddedToast;
+ if (ACTION_REMOVE_FROM_GROUP.equals(action)) return R.string.groupMembersRemovedToast;
+ throw new IllegalArgumentException("Unhanded contact save action " + action);
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mGroupMetadata == null || mGroupMetadata.memberCount < 0) {
- // Hide menu options until metatdata is fully loaded
+ // Hide menu options until metadata is fully loaded
return false;
}
super.onCreateOptionsMenu(menu);
@@ -322,22 +298,22 @@
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final boolean isSelectionMode = mActionBarAdapter.isSelectionMode();
- final boolean isSearchMode = false;
+ final boolean isSearchMode = mActionBarAdapter.isSearchMode();
- final boolean isListFragment = mPagerAdapter.isCurrentItem(FRAGMENT_MEMBERS_LIST);
- final boolean isEditorFragment = mPagerAdapter.isCurrentItem(FRAGMENT_EDITOR);
+ final boolean isGroupEditable = mGroupMetadata != null && mGroupMetadata.editable;
+ final boolean isGroupReadOnly = mGroupMetadata != null && mGroupMetadata.readOnly;
- final boolean isGroupEditable = mGroupMetadata.editable;
- final boolean isGroupReadOnly = mGroupMetadata.readOnly;
+ setVisible(menu, R.id.menu_add,
+ isGroupEditable &&!isSelectionMode && !isSearchMode);
- setVisible(menu, R.id.menu_edit_group, isGroupEditable && !isEditorFragment &&
- !isSelectionMode && !isSearchMode);
+ setVisible(menu, R.id.menu_edit_group,
+ isGroupEditable && !isSelectionMode && !isSearchMode);
- setVisible(menu, R.id.menu_delete_group, !isGroupReadOnly && !isEditorFragment &&
- !isSelectionMode && !isSearchMode);
+ setVisible(menu, R.id.menu_delete_group,
+ !isGroupReadOnly && !isSelectionMode && !isSearchMode);
setVisible(menu, R.id.menu_remove_from_group,
- isGroupEditable && isSelectionMode && isListFragment);
+ isGroupEditable && isSelectionMode);
return true;
}
@@ -356,16 +332,33 @@
onBackPressed();
return true;
}
+ case R.id.menu_add: {
+ if (mActionBarAdapter != null) {
+ mActionBarAdapter.setSearchMode(true);
+ }
+ return true;
+ }
case R.id.menu_edit_group: {
- mPagerAdapter.setCurrentItem(FRAGMENT_EDITOR);
+ GroupNameEditDialogFragment.showUpdateDialog(
+ getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG, mGroupMetadata.groupName);
return true;
}
case R.id.menu_delete_group: {
+ // TODO(wjang): add a Toast after deletion after deleting the editor fragment
GroupDeletionDialogFragment.show(getFragmentManager(), mGroupMetadata.groupId,
mGroupMetadata.groupName, /* endActivity */ true);
return true;
}
case R.id.menu_remove_from_group: {
+ if (mMembersListFragment == null) {
+ return false;
+ }
+ final int count = mMembersListFragment.getAdapter().getCount();
+ final int numSelected =
+ mMembersListFragment.getAdapter().getSelectedContactIdsArray().length;
+ Logger.logListEvent(ListEvent.ActionType.REMOVE_LABEL,
+ mMembersListFragment.getListType(), count, /* clickedIndex */ -1,
+ numSelected);
removeSelectedContacts();
return true;
}
@@ -379,25 +372,10 @@
final Intent intent = ContactSaveService.createGroupUpdateIntent(
this, mGroupMetadata.groupId, /* groupName */ null,
/* rawContactsToAdd */ null, rawContactsToRemove, getClass(),
- GroupMembersActivity.ACTION_SAVE_COMPLETED);
+ GroupMembersActivity.ACTION_REMOVE_FROM_GROUP);
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);
- }
+ mActionBarAdapter.setSelectionMode(false);
}
@Override
@@ -411,19 +389,43 @@
}
} else if (mActionBarAdapter.isSearchMode()) {
mActionBarAdapter.setSearchMode(false);
- } else if (mPagerAdapter.isCurrentItem(FRAGMENT_EDITOR)) {
- mPagerAdapter.setCurrentItem(FRAGMENT_MEMBERS_LIST);
} else {
super.onBackPressed();
}
}
- // GroupsMembersListFragment callbacks
+ private boolean isSelectAccountDialogFound() {
+ return getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_DIALOG) != null;
+ }
+
+ private boolean isGroupNameEditDialogFound() {
+ return getFragmentManager().findFragmentByTag(TAG_GROUP_NAME_EDIT_DIALOG) != null;
+ }
+
+ private void setResultCanceledAndFinish() {
+ setResultCanceledAndFinish(-1);
+ }
+
+ private void setResultCanceledAndFinish(int toastResId) {
+ if (toastResId >= 0) {
+ Toast.makeText(this, toastResId, Toast.LENGTH_SHORT).show();
+ }
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ // SelectAccountDialogFragment.Listener callbacks
@Override
- public void onGroupMemberListItemClicked(Uri contactLookupUri) {
- startActivity(ImplicitIntentsUtil.composeQuickContactIntent(
- contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED));
+ public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
+ mGroupMetadata.setGroupAccountMetadata(account);
+ GroupNameEditDialogFragment.showInsertDialog(
+ getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
+ }
+
+ @Override
+ public void onAccountSelectorCancelled() {
+ setResultCanceledAndFinish();
}
// ActionBarAdapter callbacks
@@ -441,6 +443,7 @@
mMembersListFragment.displayCheckBoxes(true);
}
invalidateOptionsMenu();
+ showFabWithAnimation(/* showFabWithAnimation = */ false);
break;
case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
mActionBarAdapter.setSearchMode(false);
@@ -474,75 +477,118 @@
@Override
public void onStartDisplayingCheckBoxes() {
mActionBarAdapter.setSelectionMode(true);
- invalidateOptionsMenu();
}
@Override
public void onSelectedContactIdsChanged() {
- if (mActionBarAdapter.isSelectionMode() && mMembersListFragment != null) {
- mActionBarAdapter.setSelectionCount(
- mMembersListFragment.getSelectedContactIds().size());
- }
- invalidateOptionsMenu();
+ mActionBarAdapter.setSelectionCount(mMembersListFragment.getSelectedContactIds().size());
}
@Override
public void onStopDisplayingCheckBoxes() {
mActionBarAdapter.setSelectionMode(false);
+ }
+
+ // GroupNameEditDialogFragment.Listener callbacks
+
+ @Override
+ public void onGroupNameEdit(String groupName) {
+ final Intent saveIntent;
+ if (mIsInsertAction) {
+ saveIntent = ContactSaveService.createNewGroupIntent(this,
+ mGroupMetadata.createAccountWithDataSet(), groupName,
+ /* rawContactsToAdd */ null, GroupMembersActivity.class,
+ GroupMembersActivity.ACTION_CREATE_GROUP);
+ } else {
+ saveIntent = ContactSaveService.createGroupRenameIntent(this,
+ mGroupMetadata.groupId, groupName, GroupMembersActivity.class,
+ GroupMembersActivity.ACTION_UPDATE_GROUP);
+ }
+ startService(saveIntent);
+ }
+
+ @Override
+ public void onGroupNameEditCancelled() {
+ if (mIsInsertAction) {
+ setResultCanceledAndFinish();
+ }
+ }
+
+ // GroupsMembersListFragment callbacks
+
+ @Override
+ public void onGroupMetadataLoaded(GroupMetadata groupMetadata) {
+ mGroupMetadata = groupMetadata;
+
+ if (!mIsInsertAction) {
+ getSupportActionBar().setTitle(mGroupMetadata.groupName);
+ }
+
+ bindAutocompleteTextView();
+ getLoaderManager().initLoader(LOADER_GROUP_MEMBERS, null, mGroupMemberCallbacks);
+
invalidateOptionsMenu();
}
- // GroupEditorFragment.Listener callbacks
+ private void bindAutocompleteTextView() {
+ final AutoCompleteTextView autoCompleteTextView =
+ (AutoCompleteTextView) mActionBarAdapter.getSearchView();
+ if (autoCompleteTextView == null) return;
+ mAutoCompleteAdapter = createAutocompleteAdapter();
+ autoCompleteTextView.setAdapter(mAutoCompleteAdapter);
+ autoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ final SuggestedMember member = (SuggestedMember) view.getTag();
+ if (member == null) {
+ return;
+ }
+ final long[] rawContactIdsToAdd = new long[1];
+ rawContactIdsToAdd[0] = member.getRawContactId();
+ final Intent intent = ContactSaveService.createGroupUpdateIntent(
+ GroupMembersActivity.this, mGroupMetadata.groupId, /* newLabel */ null,
+ rawContactIdsToAdd, /* rawContactIdsToRemove */ null,
+ GroupMembersActivity.class, GroupMembersActivity.ACTION_ADD_TO_GROUP);
+ startService(intent);
- @Override
- public void onGroupNotFound() {
- finish();
+ // Update the autocomplete adapter so the contact doesn't get suggested again
+ mAutoCompleteAdapter.addNewMember(member.getContactId());
+
+ // Clear out the text field
+ autoCompleteTextView.setText("");
+ }
+ });
}
- @Override
- public void onReverted() {
- if (mIsInsertAction) {
- finish();
- } else {
- mPagerAdapter.setCurrentItem(FRAGMENT_MEMBERS_LIST);
+ private SuggestedMemberListAdapter createAutocompleteAdapter() {
+ final SuggestedMemberListAdapter adapter = new SuggestedMemberListAdapter(
+ this, android.R.layout.simple_dropdown_item_1line);
+ adapter.setContentResolver(this.getContentResolver());
+ adapter.setAccountType(mGroupMetadata.accountType);
+ adapter.setAccountName(mGroupMetadata.accountName);
+ adapter.setDataSet(mGroupMetadata.dataSet);
+ return adapter;
+ }
+
+ private void bindAutocompleteGroupMembers(List<Member> members) {
+ if (mAutoCompleteAdapter != null) {
+ mAutoCompleteAdapter.updateExistingMembersList(members);
}
}
@Override
- public void onSaveFinished(int resultCode, Intent resultIntent) {
- if (mIsInsertAction) {
- final Intent intent = GroupUtil.createViewGroupIntent(this, resultIntent.getData());
- finish();
- startActivity(intent);
- }
+ public void onGroupMetadataLoadFailed() {
+ setResultCanceledAndFinish(R.string.groupLoadErrorToast);
}
@Override
- public void onAccountsNotFound() {
- finish();
- }
-
- @Override
- public void onGroupMemberClicked(Uri contactLookupUri) {
- startActivity(ImplicitIntentsUtil.composeQuickContactIntent(
- contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED));
- }
-
- @Override
- 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);
- }
+ public void onGroupMemberListItemClicked(int position, Uri contactLookupUri) {
+ final int count = mMembersListFragment.getAdapter().getCount();
+ Logger.logListEvent(ListEvent.ActionType.CLICK, ListEvent.ListType.GROUP, count,
+ /* clickedIndex */ position, /* numSelected */ 0);
+ final Intent intent = ImplicitIntentsUtil.composeQuickContactIntent(
+ contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED);
+ intent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.LIST_GROUP);
+ startActivity(intent);
}
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 93d3f84..2b96f5a 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -72,6 +72,7 @@
import com.android.contacts.common.list.ContactListFilterController;
import com.android.contacts.common.list.DirectoryListLoader;
import com.android.contacts.common.list.ViewPagerTabs;
+import com.android.contacts.common.logging.ListEvent;
import com.android.contacts.common.logging.Logger;
import com.android.contacts.common.logging.ScreenEvent.ScreenType;
import com.android.contacts.common.preference.ContactsPreferenceActivity;
@@ -129,6 +130,8 @@
private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
+ private static final int ACTIVITY_REQUEST_CODE_SHARE = 0;
+
private final DialogManager mDialogManager = new DialogManager(this);
private ContactsIntentResolver mIntentResolver;
@@ -398,6 +401,10 @@
mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener());
mAllFragment.setCheckBoxListListener(new CheckBoxListListener());
+ final int listType = mContactListFilterController.getFilter().filterType ==
+ ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS
+ ? ListEvent.ListType.ALL_CONTACTS : ListEvent.ListType.ACCOUNT;
+ mAllFragment.setListType(listType);
if (areGroupWritableAccountsAvailable() && mGroupsFragment != null) {
mGroupsFragment.setListener(this);
@@ -1028,7 +1035,8 @@
}
@Override
- public void onViewContactAction(Uri contactLookupUri, boolean isEnterpriseContact) {
+ public void onViewContactAction(int position, Uri contactLookupUri,
+ boolean isEnterpriseContact) {
if (isEnterpriseContact) {
// No implicit intent as user may have a different contacts app in work profile.
QuickContact.showQuickContact(PeopleActivity.this, new Rect(), contactLookupUri,
@@ -1036,8 +1044,26 @@
} else {
final Intent intent = ImplicitIntentsUtil.composeQuickContactIntent(
contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED);
- intent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE,
- mAllFragment.isSearchMode() ? ScreenType.SEARCH : ScreenType.ALL_CONTACTS);
+ final int previousScreen;
+ if (mAllFragment.isSearchMode()) {
+ previousScreen = ScreenType.SEARCH;
+ } else {
+ if (mAllFragment.getFilter().filterType ==
+ ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
+ if (position < mAllFragment.getAdapter().getNumberOfFavorites()) {
+ previousScreen = ScreenType.FAVORITES;
+ } else {
+ previousScreen = ScreenType.ALL_CONTACTS;
+ }
+ } else {
+ previousScreen = ScreenType.LIST_ACCOUNT;
+ }
+ }
+ Logger.logListEvent(ListEvent.ActionType.CLICK,
+ /* listType */ getListTypeIncludingSearch(),
+ /* count */ mAllFragment.getAdapter().getCount(),
+ /* clickedIndex */ position, /* numSelected */ 0);
+ intent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE, previousScreen);
ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
}
}
@@ -1208,6 +1234,10 @@
return true;
}
case R.id.menu_join: {
+ Logger.logListEvent(ListEvent.ActionType.LINK,
+ /* listType */ getListTypeIncludingSearch(),
+ /* count */ mAllFragment.getAdapter().getCount(), /* clickedIndex */ -1,
+ /* numSelected */ mAllFragment.getAdapter().getSelectedContactIds().size());
joinSelectedContacts();
return true;
}
@@ -1236,6 +1266,8 @@
ContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE);
intent.putExtra(ContactsPreferenceActivity.EXTRA_MODE_FULLY_EXPANDED,
QuickContactActivity.MODE_FULLY_EXPANDED);
+ intent.putExtra(ContactsPreferenceActivity.EXTRA_PREVIOUS_SCREEN_TYPE,
+ QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE);
startActivity(intent);
} else if (id == R.id.nav_help) {
HelpUtils.launchHelpAndFeedbackForMainScreen(this);
@@ -1312,7 +1344,13 @@
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType(Contacts.CONTENT_VCARD_TYPE);
intent.putExtra(Intent.EXTRA_STREAM, uri);
- ImplicitIntentsUtil.startActivityOutsideApp(this, intent);
+ try {
+ // TODO(wenyiw): show different strings based on number of contacts.
+ startActivityForResult(Intent.createChooser(intent, getText(R.string.share_via)),
+ ACTIVITY_REQUEST_CODE_SHARE);
+ } catch (final ActivityNotFoundException ex) {
+ Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
+ }
}
private void joinSelectedContacts() {
@@ -1330,9 +1368,19 @@
@Override
public void onDeletionFinished() {
+ // The parameters count and numSelected are both the number of contacts before deletion.
+ Logger.logListEvent(ListEvent.ActionType.DELETE,
+ /* listType */ getListTypeIncludingSearch(),
+ /* count */ mAllFragment.getAdapter().getCount(), /* clickedIndex */ -1,
+ /* numSelected */ mAllFragment.getSelectedContactIds().size());
mActionBarAdapter.setSelectionMode(false);
}
+ private int getListTypeIncludingSearch() {
+ return mAllFragment.isSearchMode()
+ ? ListEvent.ListType.SEARCH_RESULT : mAllFragment.getListType();
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
@@ -1342,6 +1390,12 @@
if (resultCode == RESULT_OK) {
mAllFragment.onPickerResult(data);
}
+ case ACTIVITY_REQUEST_CODE_SHARE:
+ Logger.logListEvent(ListEvent.ActionType.SHARE,
+ /* listType */ getListTypeIncludingSearch(),
+ /* count */ mAllFragment.getAdapter().getCount(), /* clickedIndex */ -1,
+ /* numSelected */ mAllFragment.getAdapter().getSelectedContactIds().size());
+
// TODO fix or remove multipicker code
// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
diff --git a/src/com/android/contacts/group/CreateGroupDialogFragment.java b/src/com/android/contacts/group/CreateGroupDialogFragment.java
deleted file mode 100644
index 777fd44..0000000
--- a/src/com/android/contacts/group/CreateGroupDialogFragment.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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, softwareateCre
- * 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.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.widget.Button;
-import android.widget.EditText;
-
-import com.android.contacts.R;
-
-/**
- * Prompts the user for the name of the new group.
- */
-public final class CreateGroupDialogFragment extends DialogFragment {
-
- private static final String TAG_CREATE_GROUP_DIALOG = "createGroup";
-
- /** Callbacks for hosts of the {@link CreateGroupDialogFragment}. */
- public interface Listener {
- void onCreateGroup(String groupName);
- void onCreateGroupCancelled();
- }
-
- private EditText mGroupNameEditText;
-
- public static <F extends Fragment & Listener> void show(
- FragmentManager fragmentManager, F targetFragment) {
- final CreateGroupDialogFragment dialog = new CreateGroupDialogFragment();
- dialog.setTargetFragment(targetFragment, /* requestCode */ 0);
- dialog.show(fragmentManager, TAG_CREATE_GROUP_DIALOG);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- // Build a dialog with two buttons and a view of a single EditText input field
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.create_group_dialog_title)
- .setView(R.layout.create_group_dialog)
- .setNegativeButton(android.R.string.cancel, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- onCreateGroupCancelled();
- dismiss();
- }
- })
- .setPositiveButton(R.string.create_group_dialog_button, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- onCreateGroup();
- }
- });
-
- // Disable the create button when the name is empty
- final AlertDialog alertDialog = builder.create();
- alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- mGroupNameEditText = (EditText) alertDialog.findViewById(android.R.id.text1);
-
- final Button createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
- createButton.setEnabled(!TextUtils.isEmpty(getGroupName()));
- mGroupNameEditText.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- createButton.setEnabled(!TextUtils.isEmpty(s));
- }
- });
- }
- });
- return alertDialog;
- }
-
- @Override
- public void onCancel(DialogInterface dialog) {
- super.onCancel(dialog);
- onCreateGroupCancelled();
- }
-
- @Override
- public void onSaveInstanceState(Bundle b) {
- setTargetFragment(null, /* requestCode */ -1);
- super.onSaveInstanceState(b);
- }
-
- private void onCreateGroupCancelled() {
- final Fragment targetFragment = getTargetFragment();
- if (targetFragment != null && targetFragment instanceof Listener) {
- ((Listener) targetFragment).onCreateGroupCancelled();
- }
- }
-
- private void onCreateGroup() {
- final Fragment targetFragment = getTargetFragment();
- if (targetFragment != null && targetFragment instanceof Listener) {
- ((Listener) targetFragment).onCreateGroup(getGroupName());
- }
- }
-
- private String getGroupName() {
- return mGroupNameEditText == null || mGroupNameEditText.getText() == null
- ? null : mGroupNameEditText.getText().toString();
- }
-}
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index a7d30a4..a22d42f 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -33,8 +33,6 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.text.TextUtils;
@@ -69,8 +67,6 @@
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
-import com.google.common.base.Objects;
-
import java.util.ArrayList;
import java.util.List;
@@ -96,6 +92,8 @@
private static final String CURRENT_EDITOR_TAG = "currentEditorForAccount";
+ private static final String ACTION_SAVE_COMPLETED = "saveCompleted";
+
public interface Listener {
/**
* Group metadata was not found, close the fragment now.
@@ -217,9 +215,9 @@
private ContentResolver mContentResolver;
private SuggestedMemberListAdapter mAutoCompleteAdapter;
- private ArrayList<Member> mListMembersToAdd = new ArrayList<Member>();
- private ArrayList<Member> mListMembersToRemove = new ArrayList<Member>();
- private ArrayList<Member> mListToDisplay = new ArrayList<Member>();
+ private ArrayList<Member> mListMembersToAdd = new ArrayList<>();
+ private ArrayList<Member> mListMembersToRemove = new ArrayList<>();
+ private ArrayList<Member> mListToDisplay = new ArrayList<>();
public static GroupEditorFragment newInstance(String action, GroupMetadata groupMetadata,
Bundle intentExtras) {
@@ -651,8 +649,7 @@
saveIntent = ContactSaveService.createNewGroupIntent(activity,
new AccountWithDataSet(mAccountName, mAccountType, mDataSet),
mGroupNameView.getText().toString(),
- membersToAddArray, activity.getClass(),
- GroupMembersActivity.ACTION_SAVE_COMPLETED);
+ membersToAddArray, activity.getClass(), 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);
@@ -664,7 +661,7 @@
saveIntent = ContactSaveService.createGroupUpdateIntent(activity,
mGroupMetadata.groupId,
getUpdatedName(), membersToAddArray, membersToRemoveArray,
- activity.getClass(), GroupMembersActivity.ACTION_SAVE_COMPLETED);
+ activity.getClass(), ACTION_SAVE_COMPLETED);
} else {
throw new IllegalStateException("Invalid intent action type " + mAction);
}
@@ -856,113 +853,6 @@
};
/**
- * This represents a single member of the current group.
- */
- public static class Member implements Parcelable {
-
- // TODO: Switch to just dealing with raw contact IDs everywhere if possible
- private final long mRawContactId;
- private final long mContactId;
- private final Uri mLookupUri;
- private final String mDisplayName;
- private final Uri mPhotoUri;
- private final String mLookupKey;
- private final long mPhotoId;
-
- public Member(long rawContactId, String lookupKey, long contactId, String displayName,
- String photoUri, long photoId) {
- mRawContactId = rawContactId;
- mContactId = contactId;
- mLookupKey = lookupKey;
- mLookupUri = Contacts.getLookupUri(contactId, lookupKey);
- mDisplayName = displayName;
- mPhotoUri = (photoUri != null) ? Uri.parse(photoUri) : null;
- mPhotoId = photoId;
- }
-
- public long getRawContactId() {
- return mRawContactId;
- }
-
- public long getContactId() {
- return mContactId;
- }
-
- public Uri getLookupUri() {
- return mLookupUri;
- }
-
- public String getLookupKey() {
- return mLookupKey;
- }
-
- public String getDisplayName() {
- return mDisplayName;
- }
-
- public Uri getPhotoUri() {
- return mPhotoUri;
- }
-
- public long getPhotoId() {
- return mPhotoId;
- }
-
- @Override
- public boolean equals(Object object) {
- if (object instanceof Member) {
- Member otherMember = (Member) object;
- return Objects.equal(mLookupUri, otherMember.getLookupUri());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return mLookupUri == null ? 0 : mLookupUri.hashCode();
- }
-
- // Parcelable
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeLong(mRawContactId);
- dest.writeLong(mContactId);
- dest.writeParcelable(mLookupUri, flags);
- dest.writeString(mLookupKey);
- dest.writeString(mDisplayName);
- dest.writeParcelable(mPhotoUri, flags);
- dest.writeLong(mPhotoId);
- }
-
- private Member(Parcel in) {
- mRawContactId = in.readLong();
- mContactId = in.readLong();
- mLookupUri = in.readParcelable(getClass().getClassLoader());
- mLookupKey = in.readString();
- mDisplayName = in.readString();
- mPhotoUri = in.readParcelable(getClass().getClassLoader());
- mPhotoId = in.readLong();
- }
-
- public static final Parcelable.Creator<Member> CREATOR = new Parcelable.Creator<Member>() {
- @Override
- public Member createFromParcel(Parcel in) {
- return new Member(in);
- }
-
- @Override
- public Member[] newArray(int size) {
- return new Member[size];
- }
- };
- }
-
- /**
* This adapter displays a list of members for the current group being edited.
*/
private final class MemberListAdapter extends BaseAdapter {
diff --git a/src/com/android/contacts/group/GroupMembersListFragment.java b/src/com/android/contacts/group/GroupMembersListFragment.java
index b896a4e..8553b9b 100644
--- a/src/com/android/contacts/group/GroupMembersListFragment.java
+++ b/src/com/android/contacts/group/GroupMembersListFragment.java
@@ -15,38 +15,136 @@
*/
package com.android.contacts.group;
-import android.content.Context;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.util.Log;
import android.view.LayoutInflater;
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.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.logging.ListEvent.ListType;
import com.android.contacts.list.MultiSelectContactsListFragment;
/** Displays the members of a group. */
public class GroupMembersListFragment extends MultiSelectContactsListFragment {
+ private static final String TAG = "GroupMembers";
+
+ private static final String KEY_GROUP_URI = "groupUri";
private static final String KEY_GROUP_METADATA = "groupMetadata";
- private static final String ARG_GROUP_METADATA = "groupMetadata";
+ private static final String ARG_GROUP_URI = "groupUri";
+
+ private static final int LOADER_GROUP_METADATA = 0;
+ private static final int LOADER_GROUP_LIST_DETAILS = 1;
/** Callbacks for hosts of {@link GroupMembersListFragment}. */
public interface GroupMembersListListener {
+ /** Invoked after group metadata for the passed in group URI has loaded. */
+ void onGroupMetadataLoaded(GroupMetadata groupMetadata);
+
+ /** Invoked if group metadata can't be loaded for the passed in group URI. */
+ void onGroupMetadataLoadFailed();
+
/** Invoked when a group member in the list is clicked. */
- void onGroupMemberListItemClicked(Uri contactLookupUri);
+ void onGroupMemberListItemClicked(int position, Uri contactLookupUri);
}
+ /** 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(getActivity(), mGroupUri);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ if (cursor == null || cursor.isClosed() || !cursor.moveToNext()) {
+ Log.e(TAG, "Failed to load group metadata for " + mGroupUri);
+ if (mListener != null) {
+ mListener.onGroupMetadataLoadFailed();
+ }
+ return;
+ }
+ // TODO(wjang): how should we handle deleted groups
+ 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(getActivity());
+ 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(getActivity());
+
+ // 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 Uri mGroupUri;
+
private GroupMembersListListener mListener;
private GroupMetadata mGroupMetadata;
- public static GroupMembersListFragment newInstance(GroupMetadata groupMetadata) {
+ public static GroupMembersListFragment newInstance(Uri groupUri) {
final Bundle args = new Bundle();
- args.putParcelable(ARG_GROUP_METADATA, groupMetadata);
+ args.putParcelable(ARG_GROUP_URI, groupUri);
final GroupMembersListFragment fragment = new GroupMembersListFragment();
fragment.setArguments(args);
@@ -61,55 +159,47 @@
// Don't show the scrollbar until after group members have been loaded
setVisibleScrollbarEnabled(false);
setQuickContactEnabled(false);
+ setListType(ListType.GROUP);
}
- @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());
- }
+ public void setListener(GroupMembersListListener listener) {
+ mListener = listener;
}
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
if (savedState == null) {
- mGroupMetadata = getArguments().getParcelable(ARG_GROUP_METADATA);
+ mGroupUri = getArguments().getParcelable(ARG_GROUP_URI);
} else {
+ mGroupUri = savedState.getParcelable(KEY_GROUP_URI);
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;
+ protected void startLoading() {
+ if (mGroupMetadata == null || !mGroupMetadata.isValid()) {
+ getLoaderManager().restartLoader(LOADER_GROUP_METADATA, null, mGroupMetadataCallbacks);
+ } else {
+ onGroupMetadataLoaded();
+ }
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
+ outState.putParcelable(KEY_GROUP_URI, mGroupUri);
outState.putParcelable(KEY_GROUP_METADATA, mGroupMetadata);
}
- private void bindMembersCount(View view) {
- final View accountFilterContainer = view.findViewById(
+ private void onGroupMetadataLoaded() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Loaded " + mGroupMetadata);
+
+ maybeAttachCheckBoxListener();
+
+ // Bind the members count
+ final View accountFilterContainer = getView().findViewById(
R.id.account_filter_header_container);
if (mGroupMetadata.memberCount >= 0) {
accountFilterContainer.setVisibility(View.VISIBLE);
@@ -122,12 +212,30 @@
} else {
accountFilterContainer.setVisibility(View.GONE);
}
+
+ if (mListener != null) {
+ mListener.onGroupMetadataLoaded(mGroupMetadata);
+ }
+
+ // Start loading the group members
+ super.startLoading();
+ }
+
+ private void maybeAttachCheckBoxListener() {
+ // Don't attach the multi select check box listener if we can't edit the group
+ if (mGroupMetadata != null && mGroupMetadata.editable) {
+ try {
+ setCheckBoxListListener((OnCheckBoxListActionListener) getActivity());
+ } catch (ClassCastException e) {
+ throw new ClassCastException(getActivity() + " must implement " +
+ OnCheckBoxListActionListener.class.getSimpleName());
+ }
+ }
}
@Override
protected GroupMembersListAdapter createListAdapter() {
final GroupMembersListAdapter adapter = new GroupMembersListAdapter(getContext());
- adapter.setSectionHeaderDisplayEnabled(true);
adapter.setDisplayPhotos(true);
return adapter;
}
@@ -140,7 +248,9 @@
@Override
protected void configureAdapter() {
super.configureAdapter();
- getAdapter().setGroupId(mGroupMetadata.groupId);
+ if (mGroupMetadata != null) {
+ getAdapter().setGroupId(mGroupMetadata.groupId);
+ }
}
@Override
@@ -160,7 +270,7 @@
}
if (mListener != null) {
final Uri contactLookupUri = getAdapter().getContactLookupUri(position);
- mListener.onGroupMemberListItemClicked(contactLookupUri);
+ mListener.onGroupMemberListItemClicked(position, contactLookupUri);
}
}
}
diff --git a/src/com/android/contacts/group/GroupMetadata.java b/src/com/android/contacts/group/GroupMetadata.java
index 788c1d4..fcf5dc2 100644
--- a/src/com/android/contacts/group/GroupMetadata.java
+++ b/src/com/android/contacts/group/GroupMetadata.java
@@ -18,6 +18,9 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.contacts.common.model.account.AccountWithDataSet;
/** Meta data for a contact group. */
// TODO(wjang): consolidate with com.android.contacts.common.GroupMetaData;
@@ -39,7 +42,7 @@
public String accountName;
public String accountType;
public String dataSet;
- public long groupId;
+ public long groupId = -1;
public String groupName;
public boolean readOnly;
public boolean editable;
@@ -48,7 +51,7 @@
public GroupMetadata() {
}
- public GroupMetadata(Parcel source) {
+ private GroupMetadata(Parcel source) {
readFromParcel(source);
}
@@ -77,6 +80,25 @@
dest.writeInt(memberCount);
}
+ /** Whether all metadata fields are set. */
+ public boolean isValid() {
+ return uri != null
+ && !TextUtils.isEmpty(accountName)
+ && !TextUtils.isEmpty(groupName)
+ && groupId > 0
+ && memberCount >= 0;
+ }
+
+ public AccountWithDataSet createAccountWithDataSet() {
+ return new AccountWithDataSet(accountName, accountType, dataSet);
+ }
+
+ public void setGroupAccountMetadata(AccountWithDataSet account) {
+ accountName = account.name;
+ accountType = account.type;
+ dataSet = account.dataSet;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -93,6 +115,7 @@
" readOnly=" + readOnly +
" editable=" + editable +
" memberCount=" + memberCount +
+ " isValid=" + isValid() +
"]";
}
}
\ No newline at end of file
diff --git a/src/com/android/contacts/group/GroupNameEditDialogFragment.java b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
new file mode 100644
index 0000000..f7ee6bf
--- /dev/null
+++ b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
@@ -0,0 +1,165 @@
+/*
+ * 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, softwareateCre
+ * 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.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.android.contacts.R;
+
+/**
+ * Edits the name of a group.
+ */
+public final class GroupNameEditDialogFragment extends DialogFragment {
+
+ private static final String KEY_IS_INSERT = "isInsert";
+ private static final String KEY_GROUP_NAME = "groupName";
+
+ private static final String ARG_IS_INSERT = "isInsert";
+ private static final String ARG_GROUP_NAME = "groupName";
+
+ /** Callbacks for hosts of the {@link GroupNameEditDialogFragment}. */
+ public interface Listener {
+ void onGroupNameEdit(String groupName);
+ void onGroupNameEditCancelled();
+ }
+
+ private boolean mIsInsert;
+ private String mGroupName;
+ private EditText mGroupNameEditText;
+
+ public static void showInsertDialog(FragmentManager fragmentManager, String tag) {
+ showDialog(fragmentManager, tag, /* isInsert */ true, /* groupName */ null);
+ }
+
+ public static void showUpdateDialog(FragmentManager fragmentManager,
+ String tag, String groupName) {
+ showDialog(fragmentManager, tag, /* isInsert */ false, groupName);
+ }
+
+ private static void showDialog(FragmentManager fragmentManager,
+ String tag, boolean isInsert, String groupName) {
+ final Bundle args = new Bundle();
+ args.putBoolean(ARG_IS_INSERT, isInsert);
+ args.putString(ARG_GROUP_NAME, groupName);
+
+ final GroupNameEditDialogFragment dialog = new GroupNameEditDialogFragment();
+ dialog.setArguments(args);
+ dialog.show(fragmentManager, tag);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState == null) {
+ final Bundle args = getArguments();
+ mIsInsert = args.getBoolean(KEY_IS_INSERT);
+ mGroupName = args.getString(KEY_GROUP_NAME);
+ } else {
+ mIsInsert = savedInstanceState.getBoolean(ARG_IS_INSERT);
+ mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ // Build a dialog with two buttons and a view of a single EditText input field
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(mIsInsert ? R.string.insert_group_dialog_title
+ : R.string.update_group_dialog_title)
+ .setView(R.layout.group_name_edit_dialog)
+ .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getListener().onGroupNameEditCancelled();
+ dismiss();
+ }
+ })
+ .setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getListener().onGroupNameEdit(getGroupName());
+ }
+ });
+
+ // Disable the create button when the name is empty
+ final AlertDialog alertDialog = builder.create();
+ alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialog) {
+ mGroupNameEditText = (EditText) alertDialog.findViewById(android.R.id.text1);
+ if (!TextUtils.isEmpty(mGroupName)) {
+ mGroupNameEditText.setText(mGroupName);
+ mGroupNameEditText.setSelection(mGroupName.length());
+ }
+
+ final Button createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ createButton.setEnabled(!TextUtils.isEmpty(getGroupName()));
+ mGroupNameEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ createButton.setEnabled(!TextUtils.isEmpty(s));
+ }
+ });
+ }
+ });
+ return alertDialog;
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+ getListener().onGroupNameEditCancelled();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_IS_INSERT, mIsInsert);
+ outState.putString(KEY_GROUP_NAME, getGroupName());
+ }
+
+ private Listener getListener() {
+ if (!(getActivity() instanceof Listener)) {
+ throw new ClassCastException(getActivity() + " must implement " +
+ Listener.class.getName());
+ }
+ return (Listener) getActivity();
+ }
+
+ private String getGroupName() {
+ return mGroupNameEditText == null || mGroupNameEditText.getText() == null
+ ? null : mGroupNameEditText.getText().toString();
+ }
+}
diff --git a/src/com/android/contacts/group/Member.java b/src/com/android/contacts/group/Member.java
new file mode 100644
index 0000000..f8c56fd
--- /dev/null
+++ b/src/com/android/contacts/group/Member.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.contacts.group;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.ContactsContract.Contacts;
+
+import com.google.common.base.Objects;
+
+/** A member of the group currently being displayed to the user. */
+public class Member implements Parcelable {
+
+ public static final Parcelable.Creator<Member> CREATOR = new Parcelable.Creator<Member>() {
+ @Override
+ public Member createFromParcel(Parcel in) {
+ return new Member(in);
+ }
+
+ @Override
+ public Member[] newArray(int size) {
+ return new Member[size];
+ }
+ };
+
+ // TODO: Switch to just dealing with raw contact IDs everywhere if possible
+ private final long mRawContactId;
+ private final long mContactId;
+ private final Uri mLookupUri;
+ private final String mDisplayName;
+ private final Uri mPhotoUri;
+ private final String mLookupKey;
+ private final long mPhotoId;
+
+ public Member(long rawContactId, String lookupKey, long contactId, String displayName,
+ String photoUri, long photoId) {
+ mRawContactId = rawContactId;
+ mContactId = contactId;
+ mLookupKey = lookupKey;
+ mLookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ mDisplayName = displayName;
+ mPhotoUri = (photoUri != null) ? Uri.parse(photoUri) : null;
+ mPhotoId = photoId;
+ }
+
+ public long getRawContactId() {
+ return mRawContactId;
+ }
+
+ public long getContactId() {
+ return mContactId;
+ }
+
+ public Uri getLookupUri() {
+ return mLookupUri;
+ }
+
+ public String getLookupKey() {
+ return mLookupKey;
+ }
+
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ public Uri getPhotoUri() {
+ return mPhotoUri;
+ }
+
+ public long getPhotoId() {
+ return mPhotoId;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof Member) {
+ Member otherMember = (Member) object;
+ return Objects.equal(mLookupUri, otherMember.getLookupUri());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mLookupUri == null ? 0 : mLookupUri.hashCode();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mRawContactId);
+ dest.writeLong(mContactId);
+ dest.writeParcelable(mLookupUri, flags);
+ dest.writeString(mLookupKey);
+ dest.writeString(mDisplayName);
+ dest.writeParcelable(mPhotoUri, flags);
+ dest.writeLong(mPhotoId);
+ }
+
+ private Member(Parcel in) {
+ mRawContactId = in.readLong();
+ mContactId = in.readLong();
+ mLookupUri = in.readParcelable(getClass().getClassLoader());
+ mLookupKey = in.readString();
+ mDisplayName = in.readString();
+ mPhotoUri = in.readParcelable(getClass().getClassLoader());
+ mPhotoId = in.readLong();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/group/SuggestedMemberListAdapter.java b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
index eb80ac4..efcb79f 100644
--- a/src/com/android/contacts/group/SuggestedMemberListAdapter.java
+++ b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
@@ -118,9 +118,9 @@
mContentResolver = resolver;
}
- public void updateExistingMembersList(List<GroupEditorFragment.Member> list) {
+ public void updateExistingMembersList(List<Member> list) {
mExistingMemberContactIds.clear();
- for (GroupEditorFragment.Member member : list) {
+ for (Member member : list) {
mExistingMemberContactIds.add(member.getContactId());
}
}
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index ea55333..436c3a2 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -39,6 +39,7 @@
import com.android.contacts.common.list.ContactListAdapter;
import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.common.list.DirectoryPartition;
+import com.android.contacts.common.logging.ListEvent.ListType;
import com.android.contacts.common.util.ContactLoaderUtils;
import java.util.List;
@@ -203,11 +204,16 @@
}
if (mFilter != null && mFilter.equals(filter)) {
+ setLogListEvents(false);
return;
}
Log.v(TAG, "New filter: " + filter);
+ setListType(filter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS
+ ? ListType.ALL_CONTACTS : ListType.ACCOUNT);
+ setLogListEvents(true);
+
mFilter = filter;
mLastSelectedPosition = -1;
saveFilter();
@@ -604,9 +610,10 @@
mListener = listener;
}
- public void viewContact(Uri contactUri, boolean isEnterpriseContact) {
+ public void viewContact(int position, Uri contactUri, boolean isEnterpriseContact) {
setSelectedContactUri(contactUri, false, false, true, false);
- if (mListener != null) mListener.onViewContactAction(contactUri, isEnterpriseContact);
+ if (mListener != null) mListener.onViewContactAction(position, contactUri,
+ isEnterpriseContact);
}
public void deleteContact(Uri contactUri) {
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 97bb86a..aad99d1 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -67,7 +67,7 @@
super.onItemClick(position, id);
return;
}
- viewContact(uri, getAdapter().isEnterpriseContact(position));
+ viewContact(position, uri, getAdapter().isEnterpriseContact(position));
}
@Override
diff --git a/src/com/android/contacts/list/MultiSelectContactsListFragment.java b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
index 53f5a74..682f39b 100644
--- a/src/com/android/contacts/list/MultiSelectContactsListFragment.java
+++ b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
@@ -19,8 +19,9 @@
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.common.logging.ListEvent.ActionType;
import com.android.contacts.common.logging.Logger;
+import com.android.contacts.common.logging.SearchState;
import android.database.Cursor;
import android.os.Bundle;
@@ -148,8 +149,11 @@
mCheckBoxListListener.onStartDisplayingCheckBoxes();
}
getAdapter().toggleSelectionOfContactId(contactId);
+ Logger.logListEvent(ActionType.SELECT, getListType(),
+ /* count */ getAdapter().getCount(), /* clickedIndex */ position,
+ /* numSelected */ 1);
// Manually send clicked event if there is a checkbox.
- // See b/24098561. TalkBack will not read it otherwise.
+ // See b/24098561. TalkBack will not read it otherwise.
final int index = position + getListView().getHeaderViewsCount() - getListView()
.getFirstVisiblePosition();
if (index >= 0 && index < getListView().getChildCount()) {
diff --git a/src/com/android/contacts/list/OnContactBrowserActionListener.java b/src/com/android/contacts/list/OnContactBrowserActionListener.java
index 59fc611..5c046d1 100644
--- a/src/com/android/contacts/list/OnContactBrowserActionListener.java
+++ b/src/com/android/contacts/list/OnContactBrowserActionListener.java
@@ -31,9 +31,10 @@
/**
* Opens the specified contact for viewing.
*
- * @param contactLookupUri The lookup-uri of the Contact that should be opened
+ * @param position The index of the contact that should be opened
+ * @param contactLookupUri The lookup-uri of the contact that should be opened
*/
- void onViewContactAction(Uri contactLookupUri, boolean isEnterpriseContact);
+ void onViewContactAction(int position, Uri contactLookupUri, boolean isEnterpriseContact);
/**
* Initiates the contact deletion process.
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index 762ff6c..db6d80a 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -638,6 +638,19 @@
}
}
+ public void setEntrySubHeaderColor(int color) {
+ if (mEntries != null) {
+ for (List<View> entryList : mEntryViews) {
+ for (View entryView : entryList) {
+ final TextView subHeader = (TextView) entryView.findViewById(R.id.sub_header);
+ if (subHeader != null) {
+ subHeader.setTextColor(color);
+ }
+ }
+ }
+ }
+ }
+
/**
* The ColorFilter is passed in along with the color so that a new one only needs to be created
* once for the entire activity.
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 7942eec..78de21e 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -69,6 +69,7 @@
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.RawContacts;
+import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.CardView;
@@ -148,6 +149,7 @@
import com.android.contacts.common.util.DateUtils;
import com.android.contacts.common.util.MaterialColorMapUtils;
import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.util.UriUtils;
import com.android.contacts.common.util.ViewUtil;
import com.android.contacts.detail.ContactDisplayUtils;
@@ -259,6 +261,7 @@
private ExpandingEntryCardView mNoContactDetailsCard;
private ExpandingEntryCardView mRecentCard;
private ExpandingEntryCardView mAboutCard;
+ private ExpandingEntryCardView mPermissionExplanationCard;
// Suggestion card.
private CardView mCollapsedSuggestionCardView;
@@ -276,6 +279,10 @@
private boolean mSuggestionsShouldAutoSelected = true;
private long mPreviousContactId = 0;
+ // Permission explanation card.
+ private boolean mShouldShowPermissionExplanation = false;
+ private String mPermissionExplanationCardSubHeader = "";
+
private MultiShrinkScroller mScroller;
private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
@@ -363,6 +370,7 @@
private static final int MIN_NUM_CONTACT_ENTRIES_SHOWN = 3;
private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
private static final int CARD_ENTRY_ID_EDIT_CONTACT = -2;
+ private static final int CARD_ENTRY_ID_REQUEST_PERMISSION = -3;
private static final String KEY_LOADER_EXTRA_PHONES =
QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_PHONES";
private static final String KEY_LOADER_EXTRA_SIP_NUMBERS =
@@ -399,6 +407,13 @@
return;
}
+ if (dataId == CARD_ENTRY_ID_REQUEST_PERMISSION) {
+ finish();
+ RequestDesiredPermissionsActivity.startPermissionActivity(
+ QuickContactActivity.this);
+ return;
+ }
+
// Pass the touch point through the intent for use in the InCallUI
if (Intent.ACTION_CALL.equals(intent.getAction())) {
if (TouchPointManager.getInstance().hasValidPoint()) {
@@ -926,11 +941,43 @@
Trace.beginSection("onCreate()");
super.onCreate(savedInstanceState);
- if (RequestPermissionsActivity.startPermissionActivity(this) ||
- RequestDesiredPermissionsActivity.startPermissionActivity(this)) {
+ if (RequestPermissionsActivity.startPermissionActivity(this)) {
return;
}
+ // There're 3 states for each permission:
+ // 1. App doesn't have permission, not asked user yet.
+ // 2. App doesn't have permission, user denied it previously.
+ // 3. App has permission.
+ // Permission explanation card is displayed only for case 1.
+ final boolean hasCalendarPermission = PermissionsUtil.hasPermission(
+ this, RequestDesiredPermissionsActivity.DESIRED_PERMISSIONS[0]);
+ final boolean hasSMSPermission = PermissionsUtil.hasPermission(
+ this, RequestDesiredPermissionsActivity.DESIRED_PERMISSIONS[1]);
+
+ final boolean wasCalendarPermissionDenied =
+ ActivityCompat.shouldShowRequestPermissionRationale(
+ this, RequestDesiredPermissionsActivity.DESIRED_PERMISSIONS[0]);
+ final boolean wasSMSPermissionDenied =
+ ActivityCompat.shouldShowRequestPermissionRationale(
+ this, RequestDesiredPermissionsActivity.DESIRED_PERMISSIONS[1]);
+
+ final boolean shouldDisplayCalendarMessage =
+ !hasCalendarPermission && !wasCalendarPermissionDenied;
+ final boolean shouldDisplaySMSMessage = !hasSMSPermission && !wasSMSPermissionDenied;
+ mShouldShowPermissionExplanation = shouldDisplayCalendarMessage || shouldDisplaySMSMessage;
+
+ if (shouldDisplayCalendarMessage && shouldDisplaySMSMessage) {
+ mPermissionExplanationCardSubHeader =
+ getString(R.string.permission_explanation_subheader_calendar_and_SMS);
+ } else if (shouldDisplayCalendarMessage) {
+ mPermissionExplanationCardSubHeader =
+ getString(R.string.permission_explanation_subheader_calendar);
+ } else if (shouldDisplaySMSMessage) {
+ mPermissionExplanationCardSubHeader =
+ getString(R.string.permission_explanation_subheader_SMS);
+ }
+
final int previousScreenType = getIntent().getIntExtra
(EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.UNKNOWN);
Logger.logScreenView(this, ScreenType.QUICK_CONTACT, previousScreenType);
@@ -955,6 +1002,8 @@
mNoContactDetailsCard = (ExpandingEntryCardView) findViewById(R.id.no_contact_data_card);
mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card);
mAboutCard = (ExpandingEntryCardView) findViewById(R.id.about_card);
+ mPermissionExplanationCard =
+ (ExpandingEntryCardView) findViewById(R.id.permission_explanation_card);
mCollapsedSuggestionCardView = (CardView) findViewById(R.id.collapsed_suggestion_card);
mExpandSuggestionCardView = (CardView) findViewById(R.id.expand_suggestion_card);
@@ -1006,6 +1055,7 @@
}
});
+ mPermissionExplanationCard.setOnClickListener(mEntryClickHandler);
mNoContactDetailsCard.setOnClickListener(mEntryClickHandler);
mContactCard.setOnClickListener(mEntryClickHandler);
mContactCard.setExpandButtonText(
@@ -2612,6 +2662,49 @@
}
Trace.endSection();
+ Trace.beginSection("initialize permission explanation card");
+
+ final Drawable historyIcon = getResources().getDrawable(
+ R.drawable.ic_history_24dp).mutate();
+ final Entry permissionExplanationEntry = new Entry(CARD_ENTRY_ID_REQUEST_PERMISSION,
+ historyIcon, getString(R.string.permission_explanation_header),
+ mPermissionExplanationCardSubHeader, /* subHeaderIcon = */ null,
+ /* text = */ null, /* textIcon = */ null,
+ /* primaryContentDescription = */ null, getIntent(),
+ /* alternateIcon = */ null, /* alternateIntent = */ null,
+ /* alternateContentDescription = */ null, /* shouldApplyColor = */ true,
+ /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
+ /* thirdIcon = */ null, /* thirdIntent = */ null,
+ /* thirdContentDescription = */ null, /* thirdAction = */ Entry.ACTION_NONE,
+ /* thirdExtras = */ null, R.drawable.ic_history_24dp);
+
+ final List<List<Entry>> permissionExplanationEntries = new ArrayList<>();
+ permissionExplanationEntries.add(new ArrayList<Entry>());
+ permissionExplanationEntries.get(0).add(permissionExplanationEntry);
+
+ final int subHeaderTextColor = getResources().getColor(android.R.color.white);
+ final PorterDuffColorFilter whiteColorFilter =
+ new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP);
+
+ mPermissionExplanationCard.initialize(permissionExplanationEntries,
+ /* numInitialVisibleEntries = */ 1,
+ /* isExpanded = */ true,
+ /* isAlwaysExpanded = */ true,
+ /* listener = */ null,
+ mScroller);
+
+ mPermissionExplanationCard.setColorAndFilter(subHeaderTextColor, whiteColorFilter);
+ mPermissionExplanationCard.setBackgroundColor(mColorFilterColor);
+ mPermissionExplanationCard.setEntryHeaderColor(subHeaderTextColor);
+ mPermissionExplanationCard.setEntrySubHeaderColor(subHeaderTextColor);
+
+ if (mShouldShowPermissionExplanation) {
+ mPermissionExplanationCard.setVisibility(View.VISIBLE);
+ } else {
+ mPermissionExplanationCard.setVisibility(View.GONE);
+ }
+
+ Trace.endSection();
// About card is initialized along with the contact card, but since it appears after
// the recent card in the UI, we hold off until making it visible until the recent
diff --git a/src/com/android/contacts/widget/NoSwipeViewPager.java b/src/com/android/contacts/widget/NoSwipeViewPager.java
deleted file mode 100644
index b24df39..0000000
--- a/src/com/android/contacts/widget/NoSwipeViewPager.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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