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             data.moveToPosition(-1);
    249             if (data.moveToNext()) {
    250                 boolean deleted = data.getInt(GroupMetaDataLoader.DELETED) == 1;
    251                 if (!deleted) {
    252                     bindGroupMetaData(data);
    253 
    254                     // Retrieve the list of members
    255                     startGroupMembersLoader();
    256                     return;
    257                 }
    258             }
    259             updateSize(-1);
    260             updateTitle(null);
    261         }
    262 
    263         @Override
    264         public void onLoaderReset(Loader<Cursor> loader) {}
    265     };
    266 
    267     /**
    268      * The listener for the group members list loader
    269      */
    270     private final LoaderManager.LoaderCallbacks<Cursor> mGroupMemberListLoaderListener =
    271             new LoaderCallbacks<Cursor>() {
    272 
    273         @Override
    274         public CursorLoader onCreateLoader(int id, Bundle args) {
    275             return GroupMemberLoader.constructLoaderForGroupDetailQuery(mContext, mGroupId);
    276         }
    277 
    278         @Override
    279         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    280             updateSize(data.getCount());
    281             mAdapter.setContactCursor(data);
    282             mMemberListView.setEmptyView(mEmptyView);
    283         }
    284 
    285         @Override
    286         public void onLoaderReset(Loader<Cursor> loader) {}
    287     };
    288 
    289     private void bindGroupMetaData(Cursor cursor) {
    290         cursor.moveToPosition(-1);
    291         if (cursor.moveToNext()) {
    292             mAccountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
    293             mDataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
    294             mGroupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID);
    295             mGroupName = cursor.getString(GroupMetaDataLoader.TITLE);
    296             mIsReadOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1;
    297             updateTitle(mGroupName);
    298             // Must call invalidate so that the option menu will get updated
    299             getActivity().invalidateOptionsMenu ();
    300 
    301             final String accountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
    302             final String dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
    303             updateAccountType(accountTypeString, dataSet);
    304         }
    305     }
    306 
    307     private void updateTitle(String title) {
    308         if (mGroupTitle != null) {
    309             mGroupTitle.setText(title);
    310         } else {
    311             mListener.onGroupTitleUpdated(title);
    312         }
    313     }
    314 
    315     /**
    316      * Display the count of the number of group members.
    317      * @param size of the group (can be -1 if no size could be determined)
    318      */
    319     private void updateSize(int size) {
    320         String groupSizeString;
    321         if (size == -1) {
    322             groupSizeString = null;
    323         } else {
    324             AccountType accountType = mAccountTypeManager.getAccountType(mAccountTypeString,
    325                     mDataSet);
    326             final CharSequence dispLabel = accountType.getDisplayLabel(mContext);
    327             if (!TextUtils.isEmpty(dispLabel)) {
    328                 String groupSizeTemplateString = getResources().getQuantityString(
    329                         R.plurals.num_contacts_in_group, size);
    330                 groupSizeString = String.format(groupSizeTemplateString, size, dispLabel);
    331             } else {
    332                 String groupSizeTemplateString = getResources().getQuantityString(
    333                         R.plurals.group_list_num_contacts_in_group, size);
    334                 groupSizeString = String.format(groupSizeTemplateString, size);
    335             }
    336         }
    337 
    338         if (mGroupSize != null) {
    339             mGroupSize.setText(groupSizeString);
    340         } else {
    341             mListener.onGroupSizeUpdated(groupSizeString);
    342         }
    343     }
    344 
    345     /**
    346      * Once the account type, group source action, and group source URI have been determined
    347      * (based on the result from the {@link Loader}), then we can display this to the user in 1 of
    348      * 2 ways depending on screen size and orientation: either as a button in the action bar or as
    349      * a button in a static header on the page.
    350      * We also use isGroupMembershipEditable() of accountType to determine whether or not we should
    351      * display the Edit option in the Actionbar.
    352      */
    353     private void updateAccountType(final String accountTypeString, final String dataSet) {
    354         final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity());
    355         final AccountType accountType =
    356                 manager.getAccountType(accountTypeString, dataSet);
    357 
    358         mIsMembershipEditable = accountType.isGroupMembershipEditable();
    359 
    360         // If the group action should be shown in the action bar, then pass the data to the
    361         // listener who will take care of setting up the view and click listener. There is nothing
    362         // else to be done by this {@link Fragment}.
    363         if (mShowGroupActionInActionBar) {
    364             mListener.onAccountTypeUpdated(accountTypeString, dataSet);
    365             return;
    366         }
    367 
    368         // Otherwise, if the {@link Fragment} needs to create and setup the button, then first
    369         // verify that there is a valid action.
    370         if (!TextUtils.isEmpty(accountType.getViewGroupActivity())) {
    371             if (mGroupSourceView == null) {
    372                 mGroupSourceView = GroupDetailDisplayUtils.getNewGroupSourceView(mContext);
    373                 // Figure out how to add the view to the fragment.
    374                 // If there is a static header with a container for the group source view, insert
    375                 // the view there.
    376                 if (mGroupSourceViewContainer != null) {
    377                     mGroupSourceViewContainer.addView(mGroupSourceView);
    378                 }
    379             }
    380 
    381             // Rebind the data since this action can change if the loader returns updated data
    382             mGroupSourceView.setVisibility(View.VISIBLE);
    383             GroupDetailDisplayUtils.bindGroupSourceView(mContext, mGroupSourceView,
    384                     accountTypeString, dataSet);
    385             mGroupSourceView.setOnClickListener(new OnClickListener() {
    386                 @Override
    387                 public void onClick(View v) {
    388                     final Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupId);
    389                     final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    390                     intent.setClassName(accountType.syncAdapterPackageName,
    391                             accountType.getViewGroupActivity());
    392                     try {
    393                         startActivity(intent);
    394                     } catch (ActivityNotFoundException e) {
    395                         Log.e(TAG, "startActivity() failed: " + e);
    396                         Toast.makeText(getActivity(), R.string.missing_app,
    397                                 Toast.LENGTH_SHORT).show();
    398                     }
    399                 }
    400             });
    401         } else if (mGroupSourceView != null) {
    402             mGroupSourceView.setVisibility(View.GONE);
    403         }
    404     }
    405 
    406     @Override
    407     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
    408             int totalItemCount) {
    409     }
    410 
    411     @Override
    412     public void onScrollStateChanged(AbsListView view, int scrollState) {
    413         if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
    414             mPhotoManager.pause();
    415         } else {
    416             mPhotoManager.resume();
    417         }
    418     }
    419 
    420     @Override
    421     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
    422         inflater.inflate(R.menu.view_group, menu);
    423     }
    424 
    425     public boolean isOptionsMenuChanged() {
    426         return mOptionsMenuGroupDeletable != isGroupDeletable() &&
    427                 mOptionsMenuGroupEditable != isGroupEditableAndPresent();
    428     }
    429 
    430     public boolean isGroupDeletable() {
    431         return mGroupUri != null && !mIsReadOnly;
    432     }
    433 
    434     public boolean isGroupEditableAndPresent() {
    435         return mGroupUri != null && mIsMembershipEditable;
    436     }
    437 
    438     @Override
    439     public void onPrepareOptionsMenu(Menu menu) {
    440         mOptionsMenuGroupDeletable = isGroupDeletable() && isVisible();
    441         mOptionsMenuGroupEditable = isGroupEditableAndPresent() && isVisible();
    442 
    443         final MenuItem editMenu = menu.findItem(R.id.menu_edit_group);
    444         editMenu.setVisible(mOptionsMenuGroupEditable);
    445 
    446         final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group);
    447         deleteMenu.setVisible(mOptionsMenuGroupDeletable);
    448     }
    449 
    450     @Override
    451     public boolean onOptionsItemSelected(MenuItem item) {
    452         switch (item.getItemId()) {
    453             case R.id.menu_edit_group: {
    454                 if (mListener != null) mListener.onEditRequested(mGroupUri);
    455                 break;
    456             }
    457             case R.id.menu_delete_group: {
    458                 GroupDeletionDialogFragment.show(getFragmentManager(), mGroupId, mGroupName,
    459                         mCloseActivityAfterDelete);
    460                 return true;
    461             }
    462         }
    463         return false;
    464     }
    465 
    466     public void closeActivityAfterDelete(boolean closeActivity) {
    467         mCloseActivityAfterDelete = closeActivity;
    468     }
    469 
    470     public long getGroupId() {
    471         return mGroupId;
    472     }
    473 }
    474