First pass on group members list activity

Still missing first letter index so we're
stilling using the legacy GroupDetailActivity.

Bug 18641067

Change-Id: Ia726460edbaaa28e4017f51b4e6e016c8c9010c7
diff --git a/res/layout/group_members_activity.xml b/res/layout/group_members_activity.xml
new file mode 100644
index 0000000..e8cc594
--- /dev/null
+++ b/res/layout/group_members_activity.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/fragment_container"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b926129..eea08ca 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -299,6 +299,13 @@
         <xliff:g id="group_name">%s</xliff:g> (<xliff:g id="count">%d</xliff:g>)
     </string>
 
+    <!-- Group list header title with the number of members in the group. [CHAR LIMIT=30] -->
+    <plurals name="group_members_count">
+        <item quantity="zero">No contacts</item>
+        <item quantity="one">1 contact</item>
+        <item quantity="other"><xliff:g id="count">%d</xliff:g> contacts</item>
+    </plurals>
+
     <!-- The text displayed when the groups list is empty while displaying all groups [CHAR LIMIT=NONE] -->
     <string name="noGroups">No groups.</string>
 
diff --git a/src/com/android/contacts/activities/GroupMembersActivity.java b/src/com/android/contacts/activities/GroupMembersActivity.java
new file mode 100644
index 0000000..c6ad4a7
--- /dev/null
+++ b/src/com/android/contacts/activities/GroupMembersActivity.java
@@ -0,0 +1,89 @@
+/*
+ * 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.activities;
+
+import android.app.ActionBar;
+import android.app.FragmentManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.contacts.ContactsActivity;
+import com.android.contacts.R;
+import com.android.contacts.common.util.ImplicitIntentsUtil;
+import com.android.contacts.group.GroupMembersListFragment;
+import com.android.contacts.group.GroupMembersListFragment.GroupMembersCallbacks;
+import com.android.contacts.quickcontact.QuickContactActivity;
+
+/** Displays the members of a group. */
+public class GroupMembersActivity extends ContactsActivity implements GroupMembersCallbacks {
+
+    private static final String TAG_GROUP_MEMBERS = "group_members";
+
+    public static final String EXTRA_MEMBERS_COUNT = "membersCount";
+
+    private GroupMembersListFragment mFragment;
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        setContentView(R.layout.group_members_activity);
+
+        final ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            actionBar.setDisplayShowHomeEnabled(true);
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setDisplayShowTitleEnabled(true);
+        }
+
+        final FragmentManager fragmentManager = getFragmentManager();
+        mFragment = (GroupMembersListFragment) fragmentManager.findFragmentByTag(TAG_GROUP_MEMBERS);
+        if (mFragment == null) {
+            mFragment = new GroupMembersListFragment();
+            fragmentManager.beginTransaction()
+                    .add(R.id.fragment_container, mFragment, TAG_GROUP_MEMBERS)
+                    .commit();
+        }
+        mFragment.setGroupUri(getIntent().getData());
+        mFragment.setMembersCount(getIntent().getIntExtra(EXTRA_MEMBERS_COUNT, -1));
+        mFragment.setCallbacks(this);
+    }
+
+    @Override
+    public void onHomePressed() {
+        onBackPressed();
+    }
+
+    @Override
+    public void onGroupNameLoaded(String groupName) {
+        setTitle(groupName);
+    }
+
+    @Override
+    public void onGroupMemberClicked(Uri contactLookupUri) {
+        startActivity(ImplicitIntentsUtil.composeQuickContactIntent(
+                contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED));
+    }
+
+    @Override
+    public void onEditGroup(Uri groupUri) {
+        final Intent intent = new Intent(this, GroupEditorActivity.class);
+        intent.setData(groupUri);
+        intent.setAction(Intent.ACTION_EDIT);
+        startActivity(intent);
+    }
+}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 14637eb..0b669fb 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -928,7 +928,8 @@
                     : getString(R.string.group_name_menu_item, groupListItem.getTitle(),
                             groupListItem.getMemberCount());
             final MenuItem menuItem = menu.add(R.id.nav_groups, Menu.NONE, Menu.NONE, title);
-            menuItem.setIntent(GroupUtil.createViewGroupIntent(this, groupListItem.getGroupId()));
+            menuItem.setIntent(GroupUtil.createViewGroupIntent(
+                    this, groupListItem.getGroupId(), groupListItem.getMemberCount()));
         }
 
         // Create a menu item to add new groups
diff --git a/src/com/android/contacts/group/GroupMembersListAdapter.java b/src/com/android/contacts/group/GroupMembersListAdapter.java
new file mode 100644
index 0000000..9085644
--- /dev/null
+++ b/src/com/android/contacts/group/GroupMembersListAdapter.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.group;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.preference.ContactsPreferences;
+
+/** Group members cursor adapter. */
+public class GroupMembersListAdapter extends ContactEntryListAdapter {
+
+    private static class GroupMembersQuery {
+
+        private static final String[] PROJECTION_PRIMARY = new String[] {
+                Data.CONTACT_ID,
+                Data.PHOTO_ID,
+                Data.LOOKUP_KEY,
+                Data.CONTACT_PRESENCE,
+                Data.CONTACT_STATUS,
+                Data.DISPLAY_NAME_PRIMARY,
+        };
+
+        private static final String[] PROJECTION_ALTERNATIVE = new String[] {
+                Data.CONTACT_ID,
+                Data.PHOTO_ID,
+                Data.LOOKUP_KEY,
+                Data.CONTACT_PRESENCE,
+                Data.CONTACT_STATUS,
+                Data.DISPLAY_NAME_ALTERNATIVE,
+        };
+
+        public static final int CONTACT_ID                   = 0;
+        public static final int CONTACT_PHOTO_ID             = 1;
+        public static final int CONTACT_LOOKUP_KEY           = 2;
+        public static final int CONTACT_PRESENCE             = 3;
+        public static final int CONTACT_STATUS               = 4;
+        public static final int CONTACT_DISPLAY_NAME         = 5;
+    }
+
+    private final CharSequence mUnknownNameText;
+    private long mGroupId;
+
+    public GroupMembersListAdapter(Context context) {
+        super(context);
+        mUnknownNameText = context.getText(android.R.string.unknownName);
+    }
+
+    /** Sets the ID of the group whose members will be displayed. */
+    public void setGroupId(long groupId) {
+        mGroupId = groupId;
+    }
+
+    /** Returns the lookup Uri for the contact at the given position in the underlying cursor. */
+    public Uri getContactLookupUri(int position) {
+        final Cursor cursor = (Cursor) getItem(position);
+        final long contactId = cursor.getLong(GroupMembersQuery.CONTACT_ID);
+        final String lookupKey = cursor.getString(GroupMembersQuery.CONTACT_LOOKUP_KEY);
+        return Contacts.getLookupUri(contactId, lookupKey);
+    }
+
+    @Override
+    public void configureLoader(CursorLoader loader, long directoryId) {
+        loader.setUri(Data.CONTENT_URI.buildUpon()
+                .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                        String.valueOf(Directory.DEFAULT))
+                .build());
+
+        loader.setSelection(Data.MIMETYPE + "=?" + " AND " + GroupMembership.GROUP_ROW_ID + "=?");
+
+        final String[] selectionArgs = new String[2];
+        selectionArgs[0] = GroupMembership.CONTENT_ITEM_TYPE;
+        selectionArgs[1] = String.valueOf(mGroupId);
+        loader.setSelectionArgs(selectionArgs);
+
+        loader.setProjection(
+                getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY
+                        ? GroupMembersQuery.PROJECTION_PRIMARY
+                        : GroupMembersQuery.PROJECTION_ALTERNATIVE);
+
+        loader.setSortOrder(
+                getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY
+                        ? Data.DISPLAY_NAME_PRIMARY
+                        : Data.DISPLAY_NAME_ALTERNATIVE);
+    }
+
+    @Override
+    public String getContactDisplayName(int position) {
+        return ((Cursor) getItem(position)).getString(GroupMembersQuery.CONTACT_DISPLAY_NAME);
+    }
+
+    @Override
+    protected ContactListItemView newView(Context context, int partition, Cursor cursor,
+            int position, ViewGroup parent) {
+        final ContactListItemView view =
+                super.newView(context, partition, cursor, position, parent);
+        view.setUnknownNameText(mUnknownNameText);
+        view.setQuickContactEnabled(isQuickContactEnabled());
+        view.setIsSectionHeaderEnabled(isSectionHeaderDisplayEnabled());
+        return view;
+    }
+
+    @Override
+    protected void bindView(View v, int partition, Cursor cursor, int position) {
+        super.bindView(v, partition, cursor, position);
+        final ContactListItemView view = (ContactListItemView) v;
+        bindSectionHeaderAndDivider(view, position);
+        bindName(view, cursor);
+        bindViewId(view, cursor, GroupMembersQuery.CONTACT_ID);
+        bindPhoto(view, cursor);
+    }
+
+    private void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
+        final int section = getSectionForPosition(position);
+        if (getPositionForSection(section) == position) {
+            final String header = (String) getSections()[section];
+            view.setSectionHeader(header);
+        } else {
+            view.setSectionHeader(null);
+        }
+    }
+
+    private void bindName(ContactListItemView view, Cursor cursor) {
+        view.showDisplayName(cursor, GroupMembersQuery.CONTACT_DISPLAY_NAME,
+                getContactNameDisplayOrder());
+    }
+
+    private void bindPhoto(final ContactListItemView view, Cursor cursor) {
+        final long photoId = cursor.isNull(GroupMembersQuery.CONTACT_PHOTO_ID)
+                ? 0 : cursor.getLong(GroupMembersQuery.CONTACT_PHOTO_ID);
+        final DefaultImageRequest imageRequest = photoId == 0
+                ? getDefaultImageRequestFromCursor(cursor, GroupMembersQuery.CONTACT_DISPLAY_NAME,
+                        GroupMembersQuery.CONTACT_LOOKUP_KEY)
+                : null;
+        getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, getCircularPhotos(),
+                imageRequest);
+    }
+}
diff --git a/src/com/android/contacts/group/GroupMembersListFragment.java b/src/com/android/contacts/group/GroupMembersListFragment.java
new file mode 100644
index 0000000..442f88d
--- /dev/null
+++ b/src/com/android/contacts/group/GroupMembersListFragment.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.contacts.group;
+
+import android.app.Activity;
+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.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.contacts.GroupMetaDataLoader;
+import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListFragment;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.interactions.GroupDeletionDialogFragment;
+
+/** Displays the members of a group. */
+public class GroupMembersListFragment extends ContactEntryListFragment<GroupMembersListAdapter> {
+
+    private static final String TAG = "GroupMembersList";
+
+    private static final String KEY_GROUP_URI = "groupUri";
+    private static final String KEY_MEMBERS_COUNT = "membersCount";
+    private static final String KEY_GROUP_METADATA = "groupMetadata";
+
+    private static final int LOADER_GROUP_METADATA = 0;
+
+    /** The listener for the group metadata loader. */
+    private final LoaderCallbacks<Cursor> mGroupMetadatCallbacks = new LoaderCallbacks<Cursor>() {
+
+        @Override
+        public CursorLoader onCreateLoader(int id, Bundle args) {
+            return new GroupMetaDataLoader(getContext(), mGroupUri);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+            if (cursor == null || cursor.isClosed()) {
+                Log.e(TAG, "Failed to load group metadata");
+                return;
+            }
+            if (cursor.moveToNext()) {
+                final boolean deleted = cursor.getInt(GroupMetaDataLoader.DELETED) == 1;
+                if (!deleted) {
+                    mGroupMetadata = new GroupMetadata();
+                    mGroupMetadata.accountType = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
+                    mGroupMetadata.dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
+                    mGroupMetadata.groupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID);
+                    mGroupMetadata.groupName = cursor.getString(GroupMetaDataLoader.TITLE);
+                    mGroupMetadata.readOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1;
+
+                    final AccountTypeManager accountTypeManager =
+                            AccountTypeManager.getInstance(getContext());
+                    final AccountType accountType = accountTypeManager.getAccountType(
+                            mGroupMetadata.accountType, mGroupMetadata.dataSet);
+                    mGroupMetadata.editable = accountType.isGroupMembershipEditable();
+
+                    onGroupMetadataLoaded();
+                }
+            }
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {}
+    };
+
+    private static final class GroupMetadata implements Parcelable {
+
+        public static final Creator<GroupMetadata> CREATOR = new Creator<GroupMetadata>() {
+
+            public GroupMetadata createFromParcel(Parcel in) {
+                return new GroupMetadata(in);
+            }
+
+            public GroupMetadata[] newArray(int size) {
+                return new GroupMetadata[size];
+            }
+        };
+
+        String accountType;
+        String dataSet;
+        long groupId;
+        String groupName;
+        boolean readOnly;
+        boolean editable;
+
+        GroupMetadata() {
+        }
+
+        GroupMetadata(Parcel source) {
+            readFromParcel(source);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(accountType);
+            dest.writeString(dataSet);
+            dest.writeLong(groupId);
+            dest.writeString(groupName);
+            dest.writeInt(readOnly ? 1 : 0);
+            dest.writeInt(editable ? 1 : 0);
+        }
+
+        private void readFromParcel(Parcel source) {
+            accountType = source.readString();
+            dataSet = source.readString();
+            groupId = source.readLong();
+            groupName = source.readString();
+            readOnly = source.readInt() == 1;
+            editable = source.readInt() == 1;
+        }
+
+        @Override
+        public String toString() {
+            return "GroupMetadata[accountType=" + accountType +
+                    " dataSet=" + dataSet +
+                    " groupId=" + groupId +
+                    " groupName=" + groupName +
+                    " readOnly=" + readOnly +
+                    " editable=" + editable +
+                    "]";
+        }
+    }
+
+    /** Callbacks for hosts of {@link GroupMembersListFragment}. */
+    public interface GroupMembersCallbacks {
+
+        /** Invoked when the user hits back in the action bar. */
+        void onHomePressed();
+
+        /** Invoked after group metadata has been loaded. */
+        void onGroupNameLoaded(String groupName);
+
+        /** Invoked when a group member in the list is clicked. */
+        void onGroupMemberClicked(Uri contactLookupUri);
+
+        /** Invoked when a user chooses ot edit the group whose members are being displayed. */
+        void onEditGroup(Uri groupUri);
+    }
+
+    private Uri mGroupUri;
+    private int mMembersCount;
+    private GroupMembersCallbacks mCallbacks;
+
+    private GroupMetadata mGroupMetadata;
+
+    public GroupMembersListFragment() {
+        setHasOptionsMenu(true);
+
+        setPhotoLoaderEnabled(true);
+        setSectionHeaderDisplayEnabled(true);
+        // Don't show the scrollbar until after group members have been loaded
+        setVisibleScrollbarEnabled(false);
+        setQuickContactEnabled(false);
+    }
+
+    /** Sets the Uri of the group whose members will be displayed. */
+    public void setGroupUri(Uri groupUri) {
+        mGroupUri = groupUri;
+    }
+
+    public void setMembersCount(int membersCount) {
+        mMembersCount = membersCount;
+    }
+
+    /** Sets a listener for group member click events. */
+    public void setCallbacks(GroupMembersCallbacks callbacks) {
+        mCallbacks = callbacks;
+    }
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        if (savedState != null) {
+            mGroupUri = savedState.getParcelable(KEY_GROUP_URI);
+            mMembersCount = savedState.getInt(KEY_MEMBERS_COUNT);
+            mGroupMetadata = savedState.getParcelable(KEY_GROUP_METADATA);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(KEY_GROUP_URI, mGroupUri);
+        outState.putInt(KEY_MEMBERS_COUNT, mMembersCount);
+        outState.putParcelable(KEY_GROUP_METADATA, mGroupMetadata);
+    }
+
+    @Override
+    protected void startLoading() {
+        if (mGroupMetadata == null) {
+            getLoaderManager().restartLoader(LOADER_GROUP_METADATA, null, mGroupMetadatCallbacks);
+        } else {
+            onGroupMetadataLoaded();
+        }
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+        inflater.inflate(R.menu.view_group, menu);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        final MenuItem editMenu = menu.findItem(R.id.menu_edit_group);
+        editMenu.setVisible(isGroupEditable());
+
+        final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group);
+        deleteMenu.setVisible(isGroupDeletable());
+    }
+
+    private boolean isGroupEditable() {
+        return mGroupUri != null && mGroupMetadata != null && mGroupMetadata.editable;
+    }
+
+    private boolean isGroupDeletable() {
+        return mGroupUri != null && mGroupMetadata != null && !mGroupMetadata.readOnly;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home: {
+                if (mCallbacks != null) {
+                    mCallbacks.onHomePressed();
+                }
+                return true;
+            }
+            case R.id.menu_edit_group: {
+                if (mCallbacks != null) {
+                    mCallbacks.onEditGroup(mGroupUri);
+                }
+                break;
+            }
+            case R.id.menu_delete_group: {
+                GroupDeletionDialogFragment.show(getFragmentManager(), mGroupMetadata.groupId,
+                        mGroupMetadata.groupName, /* endActivity */ true);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void onGroupMetadataLoaded() {
+        final Activity activity = getActivity();
+        if (activity != null) activity.invalidateOptionsMenu();
+
+        // Set the title
+        if (mCallbacks != null) {
+            mCallbacks.onGroupNameLoaded(mGroupMetadata.groupName);
+        }
+
+        // Set the header
+        bindMembersCount();
+
+        // Start loading the group members
+        super.startLoading();
+    }
+
+    private void bindMembersCount() {
+        final View accountFilterContainer = getView().findViewById(
+                R.id.account_filter_header_container);
+        if (mMembersCount >= 0) {
+            accountFilterContainer.setVisibility(View.VISIBLE);
+
+            final TextView accountFilterHeader = (TextView) accountFilterContainer.findViewById(
+                    R.id.account_filter_header);
+            accountFilterHeader.setText(getResources().getQuantityString(
+                    R.plurals.group_members_count, mMembersCount, mMembersCount));
+        } else {
+            accountFilterContainer.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    protected GroupMembersListAdapter createListAdapter() {
+        final GroupMembersListAdapter adapter = new GroupMembersListAdapter(getContext());
+        adapter.setSectionHeaderDisplayEnabled(true);
+        adapter.setDisplayPhotos(true);
+        return adapter;
+    }
+
+    @Override
+    protected void configureAdapter() {
+        super.configureAdapter();
+        if (mGroupMetadata != null) {
+            getAdapter().setGroupId(mGroupMetadata.groupId);
+        }
+    }
+
+    @Override
+    protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+        return inflater.inflate(R.layout.contact_list_content, /* root */ null);
+    }
+
+    @Override
+    protected void onItemClick(int position, long id) {
+        if (mCallbacks != null) {
+            final Uri contactLookupUri = getAdapter().getContactLookupUri(position);
+            mCallbacks.onGroupMemberClicked(contactLookupUri);
+        }
+    }
+}
diff --git a/src/com/android/contacts/group/GroupUtil.java b/src/com/android/contacts/group/GroupUtil.java
index cca0741..f8e42ee 100644
--- a/src/com/android/contacts/group/GroupUtil.java
+++ b/src/com/android/contacts/group/GroupUtil.java
@@ -26,6 +26,7 @@
 import com.android.contacts.GroupListLoader;
 import com.android.contacts.activities.GroupDetailActivity;
 import com.android.contacts.activities.GroupEditorActivity;
+import com.android.contacts.activities.GroupMembersActivity;
 import com.google.common.base.Objects;
 
 /**
@@ -77,9 +78,10 @@
     }
 
     /** Returns an Intent to view the details of the group identified by the given Uri. */
-    public static Intent createViewGroupIntent(Context context, long groupId) {
+    public static Intent createViewGroupIntent(Context context, long groupId, int membersCount) {
         final Intent intent = new Intent(context, GroupDetailActivity.class);
         intent.setData(getGroupUriFromId(groupId));
+        intent.putExtra(GroupMembersActivity.EXTRA_MEMBERS_COUNT, membersCount);
         return intent;
     }