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