Add groups to the side navigation bar

Just launch the old group details Activity for now.

Bug 18641067

Change-Id: I213c88213240d5281edfeda1bc5da9180506520b
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 7f6a51f..efc45fc 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -43,4 +43,7 @@
 
     <!-- An ID to be used for contents of a custom dialog so that its state be preserved -->
     <item type="id" name="custom_dialog_content" />
+
+    <!-- Menu group ID for the contact groups -->
+    <item type="id" name="menu_groups" />
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3bf1737..b926129 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -294,6 +294,11 @@
     <!-- The text displayed when the contacts list is empty while displaying all contacts [CHAR LIMIT=NONE] -->
     <string name="noContacts">No contacts</string>
 
+    <!-- Group name menu item title when there is at least one group member. [CHAR LIMIT=NONE] -->
+    <string name="group_name_menu_item">
+        <xliff:g id="group_name">%s</xliff:g> (<xliff:g id="count">%d</xliff:g>)
+    </string>
+
     <!-- 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/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 66a29de..c3d3277 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -36,9 +36,9 @@
 import android.provider.Settings;
 import android.support.design.widget.NavigationView;
 import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.GravityCompat;
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
-import android.support.v4.view.GravityCompat;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.app.ActionBarDrawerToggle;
 import android.support.v7.widget.Toolbar;
@@ -55,14 +55,13 @@
 import android.widget.ImageButton;
 import android.widget.Toast;
 
-
 import com.android.contacts.AppCompatContactsActivity;
 import com.android.contacts.R;
 import com.android.contacts.activities.ActionBarAdapter.TabState;
 import com.android.contacts.common.ContactsUtils;
 import com.android.contacts.common.activity.RequestPermissionsActivity;
-import com.android.contacts.common.compat.TelecomManagerUtil;
 import com.android.contacts.common.compat.BlockedNumberContractCompat;
+import com.android.contacts.common.compat.TelecomManagerUtil;
 import com.android.contacts.common.dialog.ClearFrequentsDialog;
 import com.android.contacts.common.interactions.ImportExportDialogFragment;
 import com.android.contacts.common.list.ContactEntryListFragment;
@@ -81,6 +80,10 @@
 import com.android.contacts.common.widget.FloatingActionButtonController;
 import com.android.contacts.commonbind.ObjectFactory;
 import com.android.contacts.editor.EditorIntents;
+import com.android.contacts.group.GroupListItem;
+import com.android.contacts.group.GroupUtil;
+import com.android.contacts.group.GroupsFragment;
+import com.android.contacts.group.GroupsFragment.GroupsListener;
 import com.android.contacts.interactions.ContactDeletionInteraction;
 import com.android.contacts.interactions.ContactMultiDeletionInteraction;
 import com.android.contacts.interactions.ContactMultiDeletionInteraction.MultiContactDeleteListener;
@@ -114,6 +117,7 @@
         ActionBarAdapter.Listener,
         DialogManager.DialogShowingViewActivity,
         ContactListFilterController.ContactListFilterListener,
+        GroupsListener,
         ProviderStatusListener,
         MultiContactDeleteListener,
         JoinContactsListener,
@@ -152,6 +156,7 @@
      */
     private MultiSelectContactsListFragment mAllFragment;
     private ContactTileListFragment mFavoritesFragment;
+    private GroupsFragment mGroupsFragment;
 
     /** ViewPager for swipe */
     private ViewPager mTabPager;
@@ -160,6 +165,8 @@
     private String[] mTabTitles;
     private final TabPagerListener mTabPagerListener = new TabPagerListener();
 
+    private NavigationView mNavigationView;
+
     private boolean mEnableDebugMenuOptions;
 
     /**
@@ -340,10 +347,10 @@
         drawer.setDrawerListener(toggle);
         toggle.syncState();
 
-        final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
-        navigationView.setNavigationItemSelectedListener(this);
+        mNavigationView = (NavigationView) findViewById(R.id.nav_view);
+        mNavigationView.setNavigationItemSelectedListener(this);
 
-        final Menu menu = navigationView.getMenu();
+        final Menu menu = mNavigationView.getMenu();
         if (HelpUtils.isHelpAndFeedbackAvailable()) {
             final MenuItem menuItem = menu.add(/* groupId */ R.id.misc, /* itemId */ R.id.nav_help,
                     /* order */ Menu.NONE, /* titleRes */ R.string.menu_help);
@@ -352,6 +359,7 @@
 
         final String FAVORITE_TAG = "tab-pager-favorite";
         final String ALL_TAG = "tab-pager-all";
+        final String GROUPS_TAG = "groups";
 
         // Create the fragments and add as children of the view pager.
         // The pager adapter will only change the visibility; it'll never create/destroy
@@ -363,13 +371,20 @@
                 fragmentManager.findFragmentByTag(FAVORITE_TAG);
         mAllFragment = (MultiSelectContactsListFragment)
                 fragmentManager.findFragmentByTag(ALL_TAG);
+        mGroupsFragment = (GroupsFragment)
+                fragmentManager.findFragmentByTag(GROUPS_TAG);
 
         if (mFavoritesFragment == null) {
             mFavoritesFragment = new ContactTileListFragment();
-            mAllFragment = new MultiSelectContactsListFragment();
-
             transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG);
+
+            mAllFragment = new MultiSelectContactsListFragment();
             transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG);
+
+            if (areGroupWritableAccountsAvailable()) {
+                mGroupsFragment = new GroupsFragment();
+                transaction.add(mGroupsFragment, GROUPS_TAG);
+            }
         }
 
         mFavoritesFragment.setListener(mFavoritesFragmentListener);
@@ -377,10 +392,15 @@
         mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener());
         mAllFragment.setCheckBoxListListener(new CheckBoxListListener());
 
+        if (areGroupWritableAccountsAvailable()) {
+            mGroupsFragment.setListener(this);
+        }
+
         // Hide all fragments for now.  We adjust visibility when we get onSelectedTabChanged()
         // from ActionBarAdapter.
         transaction.hide(mFavoritesFragment);
         transaction.hide(mAllFragment);
+        // Groups fragment has no UI, no need to hide it
 
         transaction.commitAllowingStateLoss();
         fragmentManager.executePendingTransactions();
@@ -893,6 +913,26 @@
     }
 
     @Override
+    public void onGroupsLoaded(List<GroupListItem> groupListItems) {
+        final Menu menu = mNavigationView.getMenu();
+        menu.removeGroup(R.id.menu_groups);
+        if (groupListItems == null || groupListItems.isEmpty()) {
+            return;
+        }
+        for (GroupListItem groupListItem : groupListItems) {
+            if (groupListItem.isFirstGroupInAccount()) {
+                menu.addSubMenu(groupListItem.getAccountName());
+            }
+            final String title = groupListItem.getMemberCount() == 0 ? groupListItem.getTitle()
+                    : getString(R.string.group_name_menu_item, groupListItem.getTitle(),
+                            groupListItem.getMemberCount());
+            final MenuItem menuItem =
+                    menu.add(R.id.menu_groups, Menu.NONE, Menu.CATEGORY_SYSTEM, title);
+            menuItem.setIntent(GroupUtil.createViewGroupIntent(this, groupListItem.getGroupId()));
+        }
+    }
+
+    @Override
     public void onProviderStatusChange() {
         updateViewConfiguration(false);
     }
@@ -1164,13 +1204,6 @@
         }
     }
 
-    private void makeMenuItemEnabled(Menu menu, int itemId, boolean visible) {
-        final MenuItem item = menu.findItem(itemId);
-        if (item != null) {
-            item.setEnabled(visible);
-        }
-    }
-
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (mDisableOptionItemSelected) {
@@ -1242,7 +1275,7 @@
                 return true;
             }
         }
-        return false;
+        return super.onOptionsItemSelected(item);
     }
 
     @SuppressWarnings("StatementWithEmptyBody")
@@ -1254,6 +1287,10 @@
             startActivity(new Intent(this, ContactsPreferenceActivity.class));
         } else if (id == R.id.nav_help) {
             HelpUtils.launchHelpAndFeedbackForMainScreen(this);
+        } else if (item.getIntent() != null) {
+            ImplicitIntentsUtil.startActivityInApp(this, item.getIntent());
+        } else {
+            Log.w(TAG, "Unhandled navigation view item selection");
         }
 
         final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
diff --git a/src/com/android/contacts/group/GroupBrowseListAdapter.java b/src/com/android/contacts/group/GroupBrowseListAdapter.java
index 48751e7..71b235c 100644
--- a/src/com/android/contacts/group/GroupBrowseListAdapter.java
+++ b/src/com/android/contacts/group/GroupBrowseListAdapter.java
@@ -61,7 +61,7 @@
         if (mSelectedGroupUri == null && cursor != null && cursor.getCount() > 0) {
             GroupListItem firstItem = getItem(0);
             long groupId = (firstItem == null) ? 0 : firstItem.getGroupId();
-            mSelectedGroupUri = getGroupUriFromId(groupId);
+            mSelectedGroupUri = GroupUtil.getGroupUriFromId(groupId);
         }
 
         notifyDataSetChanged();
@@ -76,7 +76,7 @@
         mCursor.moveToPosition(-1);
         while (mCursor.moveToNext()) {
             long groupId = mCursor.getLong(GroupListLoader.GROUP_ID);
-            Uri uri = getGroupUriFromId(groupId);
+            Uri uri = GroupUtil.getGroupUriFromId(groupId);
             if (mSelectedGroupUri.equals(uri)) {
                   return index;
             }
@@ -113,35 +113,7 @@
 
     @Override
     public GroupListItem getItem(int position) {
-        if (mCursor == null || mCursor.isClosed() || !mCursor.moveToPosition(position)) {
-            return null;
-        }
-        String accountName = mCursor.getString(GroupListLoader.ACCOUNT_NAME);
-        String accountType = mCursor.getString(GroupListLoader.ACCOUNT_TYPE);
-        String dataSet = mCursor.getString(GroupListLoader.DATA_SET);
-        long groupId = mCursor.getLong(GroupListLoader.GROUP_ID);
-        String title = mCursor.getString(GroupListLoader.TITLE);
-        int memberCount = mCursor.getInt(GroupListLoader.MEMBER_COUNT);
-
-        // Figure out if this is the first group for this account name / account type pair by
-        // checking the previous entry. This is to determine whether or not we need to display an
-        // account header in this item.
-        int previousIndex = position - 1;
-        boolean isFirstGroupInAccount = true;
-        if (previousIndex >= 0 && mCursor.moveToPosition(previousIndex)) {
-            String previousGroupAccountName = mCursor.getString(GroupListLoader.ACCOUNT_NAME);
-            String previousGroupAccountType = mCursor.getString(GroupListLoader.ACCOUNT_TYPE);
-            String previousGroupDataSet = mCursor.getString(GroupListLoader.DATA_SET);
-
-            if (accountName.equals(previousGroupAccountName) &&
-                    accountType.equals(previousGroupAccountType) &&
-                    Objects.equal(dataSet, previousGroupDataSet)) {
-                isFirstGroupInAccount = false;
-            }
-        }
-
-        return new GroupListItem(accountName, accountType, dataSet, groupId, title,
-                isFirstGroupInAccount, memberCount);
+        return GroupUtil.getGroupListItem(mCursor, position);
     }
 
     @Override
@@ -180,7 +152,7 @@
         }
 
         // Bind the group data
-        Uri groupUri = getGroupUriFromId(entry.getGroupId());
+        Uri groupUri = GroupUtil.getGroupUriFromId(entry.getGroupId());
         String memberCountString = mContext.getResources().getQuantityString(
                 R.plurals.group_list_num_contacts_in_group, entry.getMemberCount(),
                 entry.getMemberCount());
@@ -201,10 +173,6 @@
         viewCache.accountName.setText(entry.getAccountName());
     }
 
-    private static Uri getGroupUriFromId(long groupId) {
-        return ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
-    }
-
     /**
      * Cache of the children views of a contact detail entry represented by a
      * {@link GroupListItem}
diff --git a/src/com/android/contacts/group/GroupUtil.java b/src/com/android/contacts/group/GroupUtil.java
new file mode 100644
index 0000000..f5b7db1
--- /dev/null
+++ b/src/com/android/contacts/group/GroupUtil.java
@@ -0,0 +1,82 @@
+/*
+ * 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.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Groups;
+
+import com.android.contacts.GroupListLoader;
+import com.android.contacts.activities.GroupDetailActivity;
+import com.google.common.base.Objects;
+
+/**
+ * Group utility methods.
+ */
+public final class GroupUtil {
+
+    private GroupUtil() {
+    }
+
+    /** Returns a {@link GroupListItem} read from the given cursor and position. */
+    static GroupListItem getGroupListItem(Cursor cursor, int position) {
+        if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(position)) {
+            return null;
+        }
+        String accountName = cursor.getString(GroupListLoader.ACCOUNT_NAME);
+        String accountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE);
+        String dataSet = cursor.getString(GroupListLoader.DATA_SET);
+        long groupId = cursor.getLong(GroupListLoader.GROUP_ID);
+        String title = cursor.getString(GroupListLoader.TITLE);
+        int memberCount = cursor.getInt(GroupListLoader.MEMBER_COUNT);
+
+        // Figure out if this is the first group for this account name / account type pair by
+        // checking the previous entry. This is to determine whether or not we need to display an
+        // account header in this item.
+        int previousIndex = position - 1;
+        boolean isFirstGroupInAccount = true;
+        if (previousIndex >= 0 && cursor.moveToPosition(previousIndex)) {
+            String previousGroupAccountName = cursor.getString(GroupListLoader.ACCOUNT_NAME);
+            String previousGroupAccountType = cursor.getString(GroupListLoader.ACCOUNT_TYPE);
+            String previousGroupDataSet = cursor.getString(GroupListLoader.DATA_SET);
+
+            if (accountName.equals(previousGroupAccountName) &&
+                    accountType.equals(previousGroupAccountType) &&
+                    Objects.equal(dataSet, previousGroupDataSet)) {
+                isFirstGroupInAccount = false;
+            }
+        }
+
+        return new GroupListItem(accountName, accountType, dataSet, groupId, title,
+                isFirstGroupInAccount, memberCount);
+    }
+
+    /** Returns an Intent to view the details of the group identified by the given Uri. */
+    public static Intent createViewGroupIntent(Context context, long groupId) {
+        final Intent intent = new Intent(context, GroupDetailActivity.class);
+        intent.setData(getGroupUriFromId(groupId));
+        return intent;
+    }
+
+    /** TODO: Make it private after {@link GroupBrowseListAdapter} is removed. */
+    static Uri getGroupUriFromId(long groupId) {
+        return ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/group/GroupsFragment.java b/src/com/android/contacts/group/GroupsFragment.java
new file mode 100644
index 0000000..15529c7
--- /dev/null
+++ b/src/com/android/contacts/group/GroupsFragment.java
@@ -0,0 +1,103 @@
+/*
+ * 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.Fragment;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+
+import com.android.contacts.GroupListLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loads groups and group metadata for all accounts.
+ */
+public final class GroupsFragment extends Fragment {
+
+    private static final int LOADER_GROUPS = 1;
+
+    /**
+     * Callbacks for hosts of the {@link GroupsFragment}.
+     */
+    public interface GroupsListener  {
+
+        /**
+         * Invoked after groups and group metadata have been loaded.
+         */
+        void onGroupsLoaded(List<GroupListItem> groupListItems);
+    }
+
+    /**
+     * Group meta data loader listener.
+     */
+    private final LoaderManager.LoaderCallbacks<Cursor> mGroupLoaderListener =
+            new LoaderManager.LoaderCallbacks<Cursor>() {
+
+                @Override
+                public CursorLoader onCreateLoader(int id, Bundle args) {
+                    return new GroupListLoader(mContext);
+                }
+
+                @Override
+                public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+                    mGroupListItems.clear();
+                    for (int i = 0; i < data.getCount(); i++) {
+                        if (data.moveToNext()) {
+                            mGroupListItems.add(GroupUtil.getGroupListItem(data, i));
+                        }
+                    }
+                    if (mListener != null) {
+                        mListener.onGroupsLoaded(mGroupListItems);
+                    }
+                }
+
+                public void onLoaderReset(Loader<Cursor> loader) {
+                }
+            };
+
+    private Context mContext;
+    private List<GroupListItem> mGroupListItems = new ArrayList<>();
+    private GroupsListener mListener;
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mContext = context;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mContext = null;
+    }
+
+    @Override
+    public void onStart() {
+        getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupLoaderListener);
+        super.onStart();
+    }
+
+    public void setListener(GroupsListener listener) {
+        mListener = listener;
+    }
+}