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