Home | History | Annotate | Download | only in group
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.contacts.group;
     18 
     19 import android.app.Activity;
     20 import android.app.Fragment;
     21 import android.app.LoaderManager;
     22 import android.app.LoaderManager.LoaderCallbacks;
     23 import android.content.ActivityNotFoundException;
     24 import android.content.ContentUris;
     25 import android.content.Context;
     26 import android.content.CursorLoader;
     27 import android.content.Intent;
     28 import android.content.Loader;
     29 import android.content.res.Resources;
     30 import android.database.Cursor;
     31 import android.graphics.Rect;
     32 import android.net.Uri;
     33 import android.os.Bundle;
     34 import android.provider.ContactsContract.Groups;
     35 import android.text.TextUtils;
     36 import android.util.Log;
     37 import android.view.LayoutInflater;
     38 import android.view.Menu;
     39 import android.view.MenuInflater;
     40 import android.view.MenuItem;
     41 import android.view.View;
     42 import android.view.View.OnClickListener;
     43 import android.view.ViewGroup;
     44 import android.widget.AbsListView;
     45 import android.widget.AbsListView.OnScrollListener;
     46 import android.widget.ListView;
     47 import android.widget.TextView;
     48 import android.widget.Toast;
     49 
     50 import com.android.contacts.GroupMemberLoader;
     51 import com.android.contacts.GroupMetaDataLoader;
     52 import com.android.contacts.R;
     53 import com.android.contacts.common.ContactPhotoManager;
     54 import com.android.contacts.interactions.GroupDeletionDialogFragment;
     55 import com.android.contacts.common.list.ContactTileAdapter;
     56 import com.android.contacts.common.list.ContactTileView;
     57 import com.android.contacts.list.GroupMemberTileAdapter;
     58 import com.android.contacts.common.model.AccountTypeManager;
     59 import com.android.contacts.common.model.account.AccountType;
     60 
     61 /**
     62  * Displays the details of a group and shows a list of actions possible for the group.
     63  */
     64 public class GroupDetailFragment extends Fragment implements OnScrollListener {
     65 
     66     public static interface Listener {
     67         /**
     68          * The group title has been loaded
     69          */
     70         public void onGroupTitleUpdated(String title);
     71 
     72         /**
     73          * The number of group members has been determined
     74          */
     75         public void onGroupSizeUpdated(String size);
     76 
     77         /**
     78          * The account type and dataset have been determined.
     79          */
     80         public void onAccountTypeUpdated(String accountTypeString, String dataSet);
     81 
     82         /**
     83          * User decided to go to Edit-Mode
     84          */
     85         public void onEditRequested(Uri groupUri);
     86 
     87         /**
     88          * Contact is selected and should launch details page
     89          */
     90         public void onContactSelected(Uri contactUri);
     91     }
     92 
     93     private static final String TAG = "GroupDetailFragment";
     94 
     95     private static final int LOADER_METADATA = 0;
     96     private static final int LOADER_MEMBERS = 1;
     97 
     98     private Context mContext;
     99 
    100     private View mRootView;
    101     private ViewGroup mGroupSourceViewContainer;
    102     private View mGroupSourceView;
    103     private TextView mGroupTitle;
    104     private TextView mGroupSize;
    105     private ListView mMemberListView;
    106     private View mEmptyView;
    107 
    108     private Listener mListener;
    109 
    110     private ContactTileAdapter mAdapter;
    111     private ContactPhotoManager mPhotoManager;
    112     private AccountTypeManager mAccountTypeManager;
    113 
    114     private Uri mGroupUri;
    115     private long mGroupId;
    116     private String mGroupName;
    117     private String mAccountTypeString;
    118     private String mDataSet;
    119     private boolean mIsReadOnly;
    120     private boolean mIsMembershipEditable;
    121 
    122     private boolean mShowGroupActionInActionBar;
    123     private boolean mOptionsMenuGroupDeletable;
    124     private boolean mOptionsMenuGroupEditable;
    125     private boolean mCloseActivityAfterDelete;
    126 
    127     public GroupDetailFragment() {
    128     }
    129 
    130     @Override
    131     public void onAttach(Activity activity) {
    132         super.onAttach(activity);
    133         mContext = activity;
    134         mAccountTypeManager = AccountTypeManager.getInstance(mContext);
    135 
    136         Resources res = getResources();
    137         int columnCount = res.getInteger(R.integer.contact_tile_column_count);
    138 
    139         mAdapter = new GroupMemberTileAdapter(activity, mContactTileListener, columnCount);
    140 
    141         configurePhotoLoader();
    142     }
    143 
    144     @Override
    145     public void onDetach() {
    146         super.onDetach();
    147         mContext = null;
    148     }
    149 
    150     @Override
    151     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
    152         setHasOptionsMenu(true);
    153         mRootView = inflater.inflate(R.layout.group_detail_fragment, container, false);
    154         mGroupTitle = (TextView) mRootView.findViewById(R.id.group_title);
    155         mGroupSize = (TextView) mRootView.findViewById(R.id.group_size);
    156         mGroupSourceViewContainer = (ViewGroup) mRootView.findViewById(
    157                 R.id.group_source_view_container);
    158         mEmptyView = mRootView.findViewById(android.R.id.empty);
    159         mMemberListView = (ListView) mRootView.findViewById(android.R.id.list);
    160         mMemberListView.setItemsCanFocus(true);
    161         mMemberListView.setAdapter(mAdapter);
    162 
    163         return mRootView;
    164     }
    165 
    166     public void loadGroup(Uri groupUri) {
    167         mGroupUri= groupUri;
    168         startGroupMetadataLoader();
    169     }
    170 
    171     public void setQuickContact(boolean enableQuickContact) {
    172         mAdapter.enableQuickContact(enableQuickContact);
    173     }
    174 
    175     private void configurePhotoLoader() {
    176         if (mContext != null) {
    177             if (mPhotoManager == null) {
    178                 mPhotoManager = ContactPhotoManager.getInstance(mContext);
    179             }
    180             if (mMemberListView != null) {
    181                 mMemberListView.setOnScrollListener(this);
    182             }
    183             if (mAdapter != null) {
    184                 mAdapter.setPhotoLoader(mPhotoManager);
    185             }
    186         }
    187     }
    188 
    189     public void setListener(Listener value) {
    190         mListener = value;
    191     }
    192 
    193     public void setShowGroupSourceInActionBar(boolean show) {
    194         mShowGroupActionInActionBar = show;
    195     }
    196 
    197     public Uri getGroupUri() {
    198         return mGroupUri;
    199     }
    200 
    201     /**
    202      * Start the loader to retrieve the metadata for this group.
    203      */
    204     private void startGroupMetadataLoader() {
    205         getLoaderManager().restartLoader(LOADER_METADATA, null, mGroupMetadataLoaderListener);
    206     }
    207 
    208     /**
    209      * Start the loader to retrieve the list of group members.
    210      */
    211     private void startGroupMembersLoader() {
    212         getLoaderManager().restartLoader(LOADER_MEMBERS, null, mGroupMemberListLoaderListener);
    213     }
    214 
    215     private final ContactTileView.Listener mContactTileListener =
    216             new ContactTileView.Listener() {
    217 
    218         @Override
    219         public void onContactSelected(Uri contactUri, Rect targetRect) {
    220             mListener.onContactSelected(contactUri);
    221         }
    222 
    223         @Override
    224         public void onCallNumberDirectly(String phoneNumber) {
    225             // No need to call phone number directly from People app.
    226             Log.w(TAG, "unexpected invocation of onCallNumberDirectly()");
    227         }
    228 
    229         @Override
    230         public int getApproximateTileWidth() {
    231             return getView().getWidth() / mAdapter.getColumnCount();
    232         }
    233     };
    234 
    235     /**
    236      * The listener for the group metadata loader.
    237      */
    238     private final LoaderManager.LoaderCallbacks<Cursor> mGroupMetadataLoaderListener =
    239             new LoaderCallbacks<Cursor>() {
    240 
    241         @Override
    242         public CursorLoader onCreateLoader(int id, Bundle args) {
    243             return new GroupMetaDataLoader(mContext, mGroupUri);
    244         }
    245 
    246         @Override
    247         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    248             if (data == null || data.isClosed()) {
    249                 Log.e(TAG, "Failed to load group metadata");
    250                 return;
    251             }
    252             data.moveToPosition(-1);
    253             if (data.moveToNext()) {
    254                 boolean deleted = data.getInt(GroupMetaDataLoader.DELETED) == 1;
    255                 if (!deleted) {
    256                     bindGroupMetaData(data);
    257 
    258                     // Retrieve the list of members
    259                     startGroupMembersLoader();
    260                     return;
    261                 }
    262             }
    263             updateSize(-1);
    264             updateTitle(null);
    265         }
    266 
    267         @Override
    268         public void onLoaderReset(Loader<Cursor> loader) {}
    269     };
    270 
    271     /**
    272      * The listener for the group members list loader
    273      */
    274     private final LoaderManager.LoaderCallbacks<Cursor> mGroupMemberListLoaderListener =
    275             new LoaderCallbacks<Cursor>() {
    276 
    277         @Override
    278         public CursorLoader onCreateLoader(int id, Bundle args) {
    279             return GroupMemberLoader.constructLoaderForGroupDetailQuery(mContext, mGroupId);
    280         }
    281 
    282         @Override
    283         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    284             if (data == null || data.isClosed()) {
    285                 Log.e(TAG, "Failed to load group members");
    286                 return;
    287             }
    288             updateSize(data.getCount());
    289             mAdapter.setContactCursor(data);
    290             mMemberListView.setEmptyView(mEmptyView);
    291         }
    292 
    293         @Override
    294         public void onLoaderReset(Loader<Cursor> loader) {}
    295     };
    296 
    297     private void bindGroupMetaData(Cursor cursor) {
    298         cursor.moveToPosition(-1);
    299         if (cursor.moveToNext()) {
    300             mAccountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
    301             mDataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
    302             mGroupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID);
    303             mGroupName = cursor.getString(GroupMetaDataLoader.TITLE);
    304             mIsReadOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1;
    305             updateTitle(mGroupName);
    306             // Must call invalidate so that the option menu will get updated
    307             getActivity().invalidateOptionsMenu ();
    308 
    309             final String accountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
    310             final String dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
    311             updateAccountType(accountTypeString, dataSet);
    312         }
    313     }
    314 
    315     private void updateTitle(String title) {
    316         if (mGroupTitle != null) {
    317             mGroupTitle.setText(title);
    318         } else {
    319             mListener.onGroupTitleUpdated(title);
    320         }
    321     }
    322 
    323     /**
    324      * Display the count of the number of group members.
    325      * @param size of the group (can be -1 if no size could be determined)
    326      */
    327     private void updateSize(int size) {
    328         String groupSizeString;
    329         if (size == -1) {
    330             groupSizeString = null;
    331         } else {
    332             AccountType accountType = mAccountTypeManager.getAccountType(mAccountTypeString,
    333                     mDataSet);
    334             final CharSequence dispLabel = accountType.getDisplayLabel(mContext);
    335             if (!TextUtils.isEmpty(dispLabel)) {
    336                 String groupSizeTemplateString = getResources().getQuantityString(
    337                         R.plurals.num_contacts_in_group, size);
    338                 groupSizeString = String.format(groupSizeTemplateString, size, dispLabel);
    339             } else {
    340                 String groupSizeTemplateString = getResources().getQuantityString(
    341                         R.plurals.group_list_num_contacts_in_group, size);
    342                 groupSizeString = String.format(groupSizeTemplateString, size);
    343             }
    344         }
    345 
    346         if (mGroupSize != null) {
    347             mGroupSize.setText(groupSizeString);
    348         } else {
    349             mListener.onGroupSizeUpdated(groupSizeString);
    350         }
    351     }
    352 
    353     /**
    354      * Once the account type, group source action, and group source URI have been determined
    355      * (based on the result from the {@link Loader}), then we can display this to the user in 1 of
    356      * 2 ways depending on screen size and orientation: either as a button in the action bar or as
    357      * a button in a static header on the page.
    358      * We also use isGroupMembershipEditable() of accountType to determine whether or not we should
    359      * display the Edit option in the Actionbar.
    360      */
    361     private void updateAccountType(final String accountTypeString, final String dataSet) {
    362         final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity());
    363         final AccountType accountType =
    364                 manager.getAccountType(accountTypeString, dataSet);
    365 
    366         mIsMembershipEditable = accountType.isGroupMembershipEditable();
    367 
    368         // If the group action should be shown in the action bar, then pass the data to the
    369         // listener who will take care of setting up the view and click listener. There is nothing
    370         // else to be done by this {@link Fragment}.
    371         if (mShowGroupActionInActionBar) {
    372             mListener.onAccountTypeUpdated(accountTypeString, dataSet);
    373             return;
    374         }
    375 
    376         // Otherwise, if the {@link Fragment} needs to create and setup the button, then first
    377         // verify that there is a valid action.
    378         if (!TextUtils.isEmpty(accountType.getViewGroupActivity())) {
    379             if (mGroupSourceView == null) {
    380                 mGroupSourceView = GroupDetailDisplayUtils.getNewGroupSourceView(mContext);
    381                 // Figure out how to add the view to the fragment.
    382                 // If there is a static header with a container for the group source view, insert
    383                 // the view there.
    384                 if (mGroupSourceViewContainer != null) {
    385                     mGroupSourceViewContainer.addView(mGroupSourceView);
    386                 }
    387             }
    388 
    389             // Rebind the data since this action can change if the loader returns updated data
    390             mGroupSourceView.setVisibility(View.VISIBLE);
    391             GroupDetailDisplayUtils.bindGroupSourceView(mContext, mGroupSourceView,
    392                     accountTypeString, dataSet);
    393             mGroupSourceView.setOnClickListener(new OnClickListener() {
    394                 @Override
    395                 public void onClick(View v) {
    396                     final Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupId);
    397                     final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    398                     intent.setClassName(accountType.syncAdapterPackageName,
    399                             accountType.getViewGroupActivity());
    400                     try {
    401                         startActivity(intent);
    402                     } catch (ActivityNotFoundException e) {
    403                         Log.e(TAG, "startActivity() failed: " + e);
    404                         Toast.makeText(getActivity(), R.string.missing_app,
    405                                 Toast.LENGTH_SHORT).show();
    406                     }
    407                 }
    408             });
    409         } else if (mGroupSourceView != null) {
    410             mGroupSourceView.setVisibility(View.GONE);
    411         }
    412     }
    413 
    414     @Override
    415     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
    416             int totalItemCount) {
    417     }
    418 
    419     @Override
    420     public void onScrollStateChanged(AbsListView view, int scrollState) {
    421         if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
    422             mPhotoManager.pause();
    423         } else {
    424             mPhotoManager.resume();
    425         }
    426     }
    427 
    428     @Override
    429     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
    430         inflater.inflate(R.menu.view_group, menu);
    431     }
    432 
    433     public boolean isOptionsMenuChanged() {
    434         return mOptionsMenuGroupDeletable != isGroupDeletable() &&
    435                 mOptionsMenuGroupEditable != isGroupEditableAndPresent();
    436     }
    437 
    438     public boolean isGroupDeletable() {
    439         return mGroupUri != null && !mIsReadOnly;
    440     }
    441 
    442     public boolean isGroupEditableAndPresent() {
    443         return mGroupUri != null && mIsMembershipEditable;
    444     }
    445 
    446     @Override
    447     public void onPrepareOptionsMenu(Menu menu) {
    448         mOptionsMenuGroupDeletable = isGroupDeletable() && isVisible();
    449         mOptionsMenuGroupEditable = isGroupEditableAndPresent() && isVisible();
    450 
    451         final MenuItem editMenu = menu.findItem(R.id.menu_edit_group);
    452         editMenu.setVisible(mOptionsMenuGroupEditable);
    453 
    454         final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group);
    455         deleteMenu.setVisible(mOptionsMenuGroupDeletable);
    456     }
    457 
    458     @Override
    459     public boolean onOptionsItemSelected(MenuItem item) {
    460         switch (item.getItemId()) {
    461             case R.id.menu_edit_group: {
    462                 if (mListener != null) mListener.onEditRequested(mGroupUri);
    463                 break;
    464             }
    465             case R.id.menu_delete_group: {
    466                 GroupDeletionDialogFragment.show(getFragmentManager(), mGroupId, mGroupName,
    467                         mCloseActivityAfterDelete);
    468                 return true;
    469             }
    470         }
    471         return false;
    472     }
    473 
    474     public void closeActivityAfterDelete(boolean closeActivity) {
    475         mCloseActivityAfterDelete = closeActivity;
    476     }
    477 
    478     public long getGroupId() {
    479         return mGroupId;
    480     }
    481 }
    482