Second pass on fragments navigation
1. Fix some janky animation when opening or updating groups.
a. Use FragmentTransaction#replace instead of FragmentTransaction#add
so that mAllFragment will not appear before group members are loaded.
b. Change group URI and reuse existing group fragment to reload group
members, instead of popping old group fragment and adding new group
fragment, when user opens another group from a group view.
2. Fix an error in ContactsDrawerActivity#updateFilterMenu.
3. Move code to handle new Intent from PeopleActivity to group fragment.
4. Initialize ContactListFilterController in
DefaultContactBrowseListFragment#onCreate rather than onActivityCreated.
Because onActivityCreated will be called when fragment is removed and
added back, and we don't want to set filter again and again.
5. Fix a bug where activity title is not updated when nagivating from
account A --> group X --> account A.
6. Move all group actions from GroupUtil to ContactSaveService.
7. Other minor refactoring and cleanup.
Bug: 30944495
Test: manual - navigate between fragments, rotate, press Back/Home/Recent
button, search, multi-select, modify group members,
add/delete groups, view/edit/add groups from
ContactsTests.apk and when no contacts view is shown.
Change-Id: I27c89b4125e55b67921a37f2092fde839a9f8ed4
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index a38e279..492de76 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -109,8 +109,6 @@
private static final String KEY_NEW_GROUP_ACCOUNT = "newGroupAccount";
private static final String KEY_CONTACTS_VIEW = "contactsView";
- protected static final String ACTION_CREATE_GROUP = "createGroup";
-
protected ContactsView mCurrentView;
private class ContactsActionBarDrawerToggle extends ActionBarDrawerToggle {
@@ -525,7 +523,7 @@
public void updateFilterMenu(ContactListFilter filter) {
clearCheckedMenus();
- if (filter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
+ if (filter != null && filter.isContactsFilterType()) {
if (mIdMenuMap != null && mIdMenuMap.get(R.id.nav_all_contacts) != null) {
setMenuChecked(mIdMenuMap.get(R.id.nav_all_contacts), true);
}
@@ -583,11 +581,7 @@
}
public void switchToAllContacts() {
- final Intent intent = new Intent();
- final ContactListFilter filter = AccountFilterUtil.createContactsFilter(this);
- intent.putExtra(AccountFilterActivity.EXTRA_CONTACT_LIST_FILTER, filter);
- AccountFilterUtil.handleAccountFilterResult(
- mContactListFilterController, AppCompatActivity.RESULT_OK, intent);
+ resetFilter();
final Menu menu = mNavigationView.getMenu();
final MenuItem allContacts = menu.findItem(R.id.nav_all_contacts);
@@ -596,6 +590,14 @@
setTitle(getString(R.string.contactsList));
}
+ protected void resetFilter() {
+ final Intent intent = new Intent();
+ final ContactListFilter filter = AccountFilterUtil.createContactsFilter(this);
+ intent.putExtra(AccountFilterActivity.EXTRA_CONTACT_LIST_FILTER, filter);
+ AccountFilterUtil.handleAccountFilterResult(
+ mContactListFilterController, AppCompatActivity.RESULT_OK, intent);
+ }
+
protected abstract void launchFindDuplicates();
protected abstract DefaultContactBrowseListFragment getAllFragment();
@@ -645,7 +647,8 @@
@Override
public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
mNewGroupAccount = account;
- GroupNameEditDialogFragment.newInstanceForCreation(mNewGroupAccount, ACTION_CREATE_GROUP)
+ GroupNameEditDialogFragment.newInstanceForCreation(
+ mNewGroupAccount, GroupUtil.ACTION_CREATE_GROUP)
.show(getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index eb62b76..726c288 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -49,8 +49,6 @@
import android.widget.ImageButton;
import android.widget.Toast;
-import com.android.contacts.common.model.account.AccountDisplayInfo;
-import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsDrawerActivity;
import com.android.contacts.R;
@@ -95,7 +93,9 @@
private static final String TAG = "PeopleActivity";
private static final String TAG_ALL = "contacts-all";
private static final String TAG_UNAVAILABLE = "contacts-unavailable";
+ private static final String TAG_GROUP_VIEW = "contacts-groups";
private static final String TAG_DUPLICATES = "contacts-duplicates";
+ private static final String TAG_SECOND_LEVEL = "second-level";
// Tag for DuplicatesUtilFragment.java
public static final String TAG_DUPLICATES_UTIL = "DuplicatesUtilFragment";
@@ -205,7 +205,7 @@
return String.format("%s@%d", getClass().getSimpleName(), mInstanceId);
}
- public boolean areContactsAvailable() {
+ private boolean areContactsAvailable() {
return (mProviderStatus != null) && mProviderStatus.equals(ProviderStatus.STATUS_NORMAL);
}
@@ -213,7 +213,7 @@
* Initialize fragments that are (or may not be) in the layout.
*
* For the fragments that are in the layout, we initialize them in
- * {@link #createViewsAndFragments(Bundle)} after inflating the layout.
+ * {@link #createViewsAndFragments()} after inflating the layout.
*
* However, the {@link ContactsUnavailableFragment} is a special fragment which may not
* be in the layout, so we have to do the initialization here.
@@ -263,50 +263,34 @@
@Override
protected void onNewIntent(Intent intent) {
- if (ContactsDrawerActivity.ACTION_CREATE_GROUP.equals(intent.getAction())) {
+ if (GroupUtil.ACTION_CREATE_GROUP.equals(intent.getAction())) {
mGroupUri = intent.getData();
if (mGroupUri == null) {
toast(R.string.groupSavedErrorToast);
return;
}
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + mGroupUri);
- toast(R.string.groupCreatedToast);
- switchToGroupView();
+ switchToOrUpdateGroupView(intent.getAction());
return;
}
- if (isDeleteAction(intent.getAction())) {
+ if (isGroupDeleteAction(intent.getAction())) {
toast(R.string.groupDeletedToast);
- getFragmentManager().popBackStackImmediate();
+ popSecondLevel();
mCurrentView = ContactsView.ALL_CONTACTS;
showFabWithAnimation(/* showFab */ true);
return;
}
- if (isSaveAction(intent.getAction())) {
- final Uri groupUri = intent.getData();
- if (groupUri == null) {
- getFragmentManager().popBackStackImmediate();
+ if (isGroupSaveAction(intent.getAction())) {
+ mGroupUri = intent.getData();
+ if (mGroupUri == null) {
+ popSecondLevel();
toast(R.string.groupSavedErrorToast);
return;
}
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + groupUri);
-
- mGroupUri = groupUri;
-
- toast(getToastMessageForSaveAction(intent.getAction()));
-
- if (mMembersFragment.isEditMode()) {
- // If we're removing group members one at a time, don't reload the fragment so
- // the user can continue to remove group members one by one
- if (getGroupCount() == 1) {
- // If we're deleting the last group member, exit edit mode
- onBackPressed();
- }
- } else if (!GroupUtil.ACTION_REMOVE_FROM_GROUP.equals(intent.getAction())) {
- switchToGroupView();
- invalidateOptionsMenu();
- }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + mGroupUri);
+ switchToOrUpdateGroupView(intent.getAction());
}
setIntent(intent);
@@ -332,29 +316,16 @@
invalidateOptionsMenuIfNeeded();
}
- private int getGroupCount() {
- return mMembersFragment != null && mMembersFragment.getAdapter() != null
- ? mMembersFragment.getAdapter().getCount() : -1;
- }
-
- private static boolean isDeleteAction(String action) {
+ private static boolean isGroupDeleteAction(String action) {
return GroupUtil.ACTION_DELETE_GROUP.equals(action);
}
- private static boolean isSaveAction(String action) {
+ private static boolean isGroupSaveAction(String action) {
return GroupUtil.ACTION_UPDATE_GROUP.equals(action)
|| GroupUtil.ACTION_ADD_TO_GROUP.equals(action)
|| GroupUtil.ACTION_REMOVE_FROM_GROUP.equals(action);
}
- private static int getToastMessageForSaveAction(String action) {
- if (GroupUtil.ACTION_UPDATE_GROUP.equals(action)) return R.string.groupUpdatedToast;
- if (GroupUtil.ACTION_ADD_TO_GROUP.equals(action)) return R.string.groupMembersAddedToast;
- if (GroupUtil.ACTION_REMOVE_FROM_GROUP.equals(action))
- return R.string.groupMembersRemovedToast;
- throw new IllegalArgumentException("Unhanded contact save action " + action);
- }
-
private void toast(int resId) {
if (resId >= 0) {
Toast.makeText(this, resId, Toast.LENGTH_SHORT).show();
@@ -411,9 +382,9 @@
setUpAllFragment(fragmentManager);
- if (isGroupView() && mGroupUri != null) {
+ if (isGroupView()) {
mMembersFragment = (GroupMembersFragment)
- fragmentManager.findFragmentByTag(mGroupUri.toString());
+ fragmentManager.findFragmentByTag(TAG_GROUP_VIEW);
}
// Configure floating action button
@@ -445,7 +416,7 @@
if (mShouldSwitchToGroupView && !mIsRecreatedInstance) {
mGroupUri = mRequest.getContactUri();
- switchToGroupView();
+ switchToOrUpdateGroupView(GroupUtil.ACTION_SWITCH_GROUP);
mShouldSwitchToGroupView = false;
}
}
@@ -743,7 +714,7 @@
private void invalidateOptionsMenuIfNeeded() {
if (mAllFragment != null
- || mAllFragment.getOptionsMenuContactsAvailable() != areContactsAvailable()) {
+ && mAllFragment.getOptionsMenuContactsAvailable() != areContactsAvailable()) {
invalidateOptionsMenu();
}
}
@@ -788,9 +759,7 @@
mDrawer.closeDrawer(GravityCompat.START);
} else if (isGroupView()) {
if (mMembersFragment.isEditMode()) {
- mMembersFragment.setEditMode(false);
- mMembersFragment.getActionBarAdapter().setSelectionMode(false);
- mMembersFragment.displayDeleteButtons(false);
+ mMembersFragment.exitEditMode();
} else if (mMembersFragment.getActionBarAdapter().isSelectionMode()) {
mMembersFragment.getActionBarAdapter().setSelectionMode(false);
mMembersFragment.displayCheckBoxes(false);
@@ -866,21 +835,27 @@
return;
}
mGroupUri = ContentUris.withAppendedId(ContactsContract.Groups.CONTENT_URI, groupId);
- switchToGroupView();
+ switchToOrUpdateGroupView(GroupUtil.ACTION_SWITCH_GROUP);
}
@Override
protected void onFilterMenuItemClicked(Intent intent) {
- super.onFilterMenuItemClicked(intent);
+ // We must pop second level first to "restart" mAllFragment, before changing filter.
if (isInSecondLevel()) {
- getFragmentManager().popBackStackImmediate();
+ popSecondLevel();
showFabWithAnimation(/* showFab */ true);
}
mCurrentView = ContactsView.ACCOUNT_VIEW;
+ super.onFilterMenuItemClicked(intent);
}
- private void switchToGroupView() {
- switchView(ContactsView.GROUP_VIEW);
+ private void switchToOrUpdateGroupView(String action) {
+ if (mMembersFragment != null) {
+ mCurrentView = ContactsView.GROUP_VIEW;
+ mMembersFragment.updateDisplayedGroup(mGroupUri, action);
+ } else {
+ switchView(ContactsView.GROUP_VIEW);
+ }
}
@Override
@@ -889,47 +864,39 @@
}
private void switchView(ContactsView contactsView) {
- maybePopBackStack();
mCurrentView = contactsView;
setUpNewFragment();
}
- private void maybePopBackStack() {
- final FragmentManager fragmentManager = getFragmentManager();
- if (isInSecondLevel()) {
- fragmentManager.popBackStackImmediate();
- }
- }
-
private void setUpNewFragment() {
final FragmentManager fragmentManager = getFragmentManager();
final FragmentTransaction transaction = fragmentManager.beginTransaction();
if (isGroupView()) {
mMembersFragment = GroupMembersFragment.newInstance(mGroupUri);
- transaction.add(R.id.contacts_list_container, mMembersFragment, mGroupUri.toString());
+ transaction.replace(
+ R.id.contacts_list_container, mMembersFragment, TAG_GROUP_VIEW);
} else if (isDuplicatesView()) {
final Fragment duplicatesFragment = ObjectFactory.getDuplicatesFragment();
final Fragment duplicatesUtilFragment = ObjectFactory.getDuplicatesUtilFragment();
- duplicatesUtilFragment.setTargetFragment(duplicatesFragment, /* requestCode */ 0);
- transaction.add(R.id.contacts_list_container, duplicatesFragment, TAG_DUPLICATES);
- transaction.add(duplicatesUtilFragment, TAG_DUPLICATES_UTIL);
+ if (duplicatesFragment != null && duplicatesUtilFragment != null) {
+ duplicatesUtilFragment.setTargetFragment(duplicatesFragment, /* requestCode */ 0);
+ transaction.replace(
+ R.id.contacts_list_container, duplicatesFragment, TAG_DUPLICATES);
+ transaction.add(duplicatesUtilFragment, TAG_DUPLICATES_UTIL);
+ }
}
- transaction.hide(mAllFragment);
- transaction.addToBackStack(null);
+ transaction.addToBackStack(TAG_SECOND_LEVEL);
transaction.commit();
fragmentManager.executePendingTransactions();
+ resetFilter();
showFabWithAnimation(/* showFab */ false);
}
@Override
public void switchToAllContacts() {
- final FragmentManager fragmentManager = getFragmentManager();
if (isInSecondLevel()) {
- fragmentManager.popBackStackImmediate();
- if (isGroupView()) {
- mMembersFragment = null;
- }
+ popSecondLevel();
}
mCurrentView = ContactsView.ALL_CONTACTS;
showFabWithAnimation(/* showFab */ true);
@@ -937,6 +904,12 @@
super.switchToAllContacts();
}
+ private void popSecondLevel() {
+ getFragmentManager().popBackStackImmediate(
+ TAG_SECOND_LEVEL, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ mMembersFragment = null;
+ }
+
@Override
protected DefaultContactBrowseListFragment getAllFragment() {
return mAllFragment;
diff --git a/src/com/android/contacts/group/GroupMembersFragment.java b/src/com/android/contacts/group/GroupMembersFragment.java
index 062edda..26560d6 100644
--- a/src/com/android/contacts/group/GroupMembersFragment.java
+++ b/src/com/android/contacts/group/GroupMembersFragment.java
@@ -477,10 +477,6 @@
return mIsEditMode;
}
- public void setEditMode(boolean isEditMode) {
- mIsEditMode = isEditMode;
- }
-
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
@@ -674,4 +670,51 @@
}
super.onDestroy();
}
+
+ public void updateDisplayedGroup(Uri newGroupUri, String action) {
+ if (!GroupUtil.ACTION_SWITCH_GROUP.equals(action)) {
+ toast(getToastMessageForSaveAction(action));
+ }
+
+ if (isEditMode() && getGroupCount() == 1) {
+ // If we're deleting the last group member, exit edit mode
+ exitEditMode();
+ } else if (!GroupUtil.ACTION_REMOVE_FROM_GROUP.equals(action)) {
+ mGroupUri = newGroupUri;
+ mGroupMetadata = null; // Clear mGroupMetadata to trigger a new load.
+ reloadData();
+ mActivity.invalidateOptionsMenu();
+ }
+ }
+
+ private static int getToastMessageForSaveAction(String action) {
+ switch(action) {
+ case GroupUtil.ACTION_UPDATE_GROUP:
+ return R.string.groupUpdatedToast;
+ case GroupUtil.ACTION_ADD_TO_GROUP:
+ return R.string.groupMembersAddedToast;
+ case GroupUtil.ACTION_REMOVE_FROM_GROUP:
+ return R.string.groupMembersRemovedToast;
+ case GroupUtil.ACTION_CREATE_GROUP:
+ return R.string.groupCreatedToast;
+ default:
+ throw new IllegalArgumentException("Unhandled contact save action " + action);
+ }
+ }
+
+ private void toast(int resId) {
+ if (resId >= 0) {
+ Toast.makeText(getContext(), resId, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private int getGroupCount() {
+ return getAdapter() != null ? getAdapter().getCount() : -1;
+ }
+
+ public void exitEditMode() {
+ mIsEditMode = false;
+ mActionBarAdapter.setSelectionMode(false);
+ displayDeleteButtons(false);
+ }
}
diff --git a/src/com/android/contacts/group/GroupUtil.java b/src/com/android/contacts/group/GroupUtil.java
index 372eed5..f305872 100644
--- a/src/com/android/contacts/group/GroupUtil.java
+++ b/src/com/android/contacts/group/GroupUtil.java
@@ -46,17 +46,17 @@
*/
public final class GroupUtil {
- public static final String ACTION_DELETE_GROUP = "deleteGroup";
- public static final String ACTION_UPDATE_GROUP = "updateGroup";
public static final String ACTION_ADD_TO_GROUP = "addToGroup";
+ public static final String ACTION_CREATE_GROUP = "createGroup";
+ public static final String ACTION_DELETE_GROUP = "deleteGroup";
public static final String ACTION_REMOVE_FROM_GROUP = "removeFromGroup";
+ public static final String ACTION_SWITCH_GROUP = "switchGroup";
+ public static final String ACTION_UPDATE_GROUP = "updateGroup";
// System IDs of FFC groups in Google accounts
private static final Set<String> FFC_GROUPS =
new HashSet(Arrays.asList("Friends", "Family", "Coworkers"));
- public static final String EXTRA_GROUP_NAME = "groupName";
-
private GroupUtil() {
}
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 424c55d..d877195 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -71,7 +71,6 @@
import com.android.contacts.common.model.account.AccountDisplayInfo;
import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.model.account.GoogleAccountType;
import com.android.contacts.common.util.AccountFilterUtil;
import com.android.contacts.common.util.ImplicitIntentsUtil;
import com.android.contacts.interactions.ContactDeletionInteraction;
@@ -124,10 +123,9 @@
private boolean mDisableOptionItemSelected;
private ActionBarAdapter mActionBarAdapter;
- private ContactMultiDeletionInteraction mMultiDeletionInteraction;
private ContactsDrawerActivity mActivity;
private ContactsRequest mContactsRequest;
- protected ContactListFilterController mContactListFilterController;
+ private ContactListFilterController mContactListFilterController;
private final ContactListFilterListener mFilterListener = new ContactListFilterListener() {
@Override
@@ -407,6 +405,22 @@
}
@Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ mIsRecreatedInstance = (savedState != null);
+ mContactListFilterController = ContactListFilterController.getInstance(getContext());
+ mContactListFilterController.checkFilterValidity(false);
+ mContactListFilterController.addListener(mFilterListener);
+ // Use FILTER_TYPE_ALL_ACCOUNTS filter if the instance is not a re-created one.
+ // This is useful when user upgrades app while an account filter or a custom filter was
+ // stored in sharedPreference in a previous version of Contacts app.
+ final ContactListFilter filter = mIsRecreatedInstance
+ ? mContactListFilterController.getFilter()
+ : AccountFilterUtil.createContactsFilter(getContext());
+ setContactListFilter(filter);
+ }
+
+ @Override
protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
super.onCreateView(inflater, container);
@@ -477,22 +491,11 @@
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- mIsRecreatedInstance = (savedInstanceState != null);
- mContactListFilterController = ContactListFilterController.getInstance(getContext());
- mContactListFilterController.checkFilterValidity(false);
- mContactListFilterController.addListener(mFilterListener);
-
- // Use FILTER_TYPE_ALL_ACCOUNTS filter if the instance is not a re-created one.
- // This is useful when user upgrades app while an account filter or a custom filter was
- // stored in sharedPreference in a previous version of Contacts app.
- final ContactListFilter filter = mIsRecreatedInstance
- ? mContactListFilterController.getFilter()
- : AccountFilterUtil.createContactsFilter(getContext());
- setContactListFilter(filter);
mActivity = (ContactsDrawerActivity) getActivity();
mActionBarAdapter = new ActionBarAdapter(mActivity, mActionBarListener,
- mActivity.getSupportActionBar(), mActivity.getToolbar(), R.string.enter_contact_name);
+ mActivity.getSupportActionBar(), mActivity.getToolbar(),
+ R.string.enter_contact_name);
mActionBarAdapter.setShowHomeIcon(true);
initializeActionBarAdapter(savedInstanceState);
if (isSearchMode()) {
@@ -501,7 +504,7 @@
setCheckBoxListListener(new CheckBoxListListener());
setOnContactListActionListener(new ContactBrowserActionListener());
- if (mIsRecreatedInstance && savedInstanceState.getBoolean(KEY_DELETION_IN_PROGRESS)) {
+ if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_DELETION_IN_PROGRESS)) {
deleteSelectedContacts();
}
}
@@ -627,6 +630,7 @@
mActivity.getWindow().getDecorView()
.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
+
// Determine whether the account has pullToRefresh feature
if (Flags.getInstance(getContext()).getBoolean(Experiments.PULL_TO_REFRESH)) {
setSwipeRefreshLayoutEnabledOrNot(filter);
@@ -635,12 +639,12 @@
private String getActionBarTitleForAccount(ContactListFilter filter) {
final AccountDisplayInfoFactory factory = AccountDisplayInfoFactory
- .forAllAccounts(getActivity());
+ .forAllAccounts(getContext());
final AccountDisplayInfo account = factory.getAccountDisplayInfoFor(filter);
if (account.hasGoogleAccountType()) {
return getString(R.string.title_from_google);
}
- return account.withFormattedName(getActivity(), R.string.title_from_other_accounts)
+ return account.withFormattedName(getContext(), R.string.title_from_other_accounts)
.getNameLabel().toString();
}
@@ -914,9 +918,9 @@
}
private void deleteSelectedContacts() {
- mMultiDeletionInteraction =
+ final ContactMultiDeletionInteraction multiDeletionInteraction =
ContactMultiDeletionInteraction.start(this, getSelectedContactIds());
- mMultiDeletionInteraction.setListener(new MultiDeleteListener());
+ multiDeletionInteraction.setListener(new MultiDeleteListener());
mIsDeletionInProgress = true;
}