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.ContactPhotoManager;
     49 import com.android.contacts.GroupMemberLoader;
     50 import com.android.contacts.GroupMetaDataLoader;
     51 import com.android.contacts.R;
     52 import com.android.contacts.interactions.GroupDeletionDialogFragment;
     53 import com.android.contacts.list.ContactTileAdapter;
     54 import com.android.contacts.list.ContactTileAdapter.DisplayType;
     55 import com.android.contacts.list.ContactTileView;
     56 import com.android.contacts.model.AccountTypeManager;
     57 import com.android.contacts.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 
    119     private boolean mShowGroupActionInActionBar;
    120     private boolean mOptionsMenuGroupDeletable;
    121     private boolean mOptionsMenuGroupPresent;
    122     private boolean mCloseActivityAfterDelete;
    123 
    124     public GroupDetailFragment() {
    125     }
    126 
    127     @Override
    128     public void onAttach(Activity activity) {
    129         super.onAttach(activity);
    130         mContext = activity;
    131         mAccountTypeManager = AccountTypeManager.getInstance(mContext);
    132 
    133         Resources res = getResources();
    134         int columnCount = res.getInteger(R.integer.contact_tile_column_count);
    135 
    136         mAdapter = new ContactTileAdapter(activity, mContactTileListener, columnCount,
    137                 DisplayType.GROUP_MEMBERS);
    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.setAdapter(mAdapter);
    159 
    160         return mRootView;
    161     }
    162 
    163     public void loadGroup(Uri groupUri) {
    164         mGroupUri= groupUri;
    165         startGroupMetadataLoader();
    166     }
    167 
    168     public void setQuickContact(boolean enableQuickContact) {
    169         mAdapter.enableQuickContact(enableQuickContact);
    170     }
    171 
    172     private void configurePhotoLoader() {
    173         if (mContext != null) {
    174             if (mPhotoManager == null) {
    175                 mPhotoManager = ContactPhotoManager.getInstance(mContext);
    176             }
    177             if (mMemberListView != null) {
    178                 mMemberListView.setOnScrollListener(this);
    179             }
    180             if (mAdapter != null) {
    181                 mAdapter.setPhotoLoader(mPhotoManager);
    182             }
    183         }
    184     }
    185 
    186     public void setListener(Listener value) {
    187         mListener = value;
    188     }
    189 
    190     public void setShowGroupSourceInActionBar(boolean show) {
    191         mShowGroupActionInActionBar = show;
    192     }
    193 
    194     public Uri getGroupUri() {
    195         return mGroupUri;
    196     }
    197 
    198     /**
    199      * Start the loader to retrieve the metadata for this group.
    200      */
    201     private void startGroupMetadataLoader() {
    202         getLoaderManager().restartLoader(LOADER_METADATA, null, mGroupMetadataLoaderListener);
    203     }
    204 
    205     /**
    206      * Start the loader to retrieve the list of group members.
    207      */
    208     private void startGroupMembersLoader() {
    209         getLoaderManager().restartLoader(LOADER_MEMBERS, null, mGroupMemberListLoaderListener);
    210     }
    211 
    212     private final ContactTileView.Listener mContactTileListener =
    213             new ContactTileView.Listener() {
    214 
    215         @Override
    216         public void onContactSelected(Uri contactUri, Rect targetRect) {
    217             mListener.onContactSelected(contactUri);
    218         }
    219 
    220         @Override
    221         public void onCallNumberDirectly(String phoneNumber) {
    222             // No need to call phone number directly from People app.
    223             Log.w(TAG, "unexpected invocation of onCallNumberDirectly()");
    224         }
    225 
    226         @Override
    227         public int getApproximateTileWidth() {
    228             return getView().getWidth() / mAdapter.getColumnCount();
    229         }
    230     };
    231 
    232     /**
    233      * The listener for the group metadata loader.
    234      */
    235     private final LoaderManager.LoaderCallbacks<Cursor> mGroupMetadataLoaderListener =
    236             new LoaderCallbacks<Cursor>() {
    237 
    238         @Override
    239         public CursorLoader onCreateLoader(int id, Bundle args) {
    240             return new GroupMetaDataLoader(mContext, mGroupUri);
    241         }
    242 
    243         @Override
    244         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    245             data.moveToPosition(-1);
    246             if (data.moveToNext()) {
    247                 boolean deleted = data.getInt(GroupMetaDataLoader.DELETED) == 1;
    248                 if (!deleted) {
    249                     bindGroupMetaData(data);
    250 
    251                     // Retrieve the list of members
    252                     startGroupMembersLoader();
    253                     return;
    254                 }
    255             }
    256             updateSize(-1);
    257             updateTitle(null);
    258         }
    259 
    260         @Override
    261         public void onLoaderReset(Loader<Cursor> loader) {}
    262     };
    263 
    264     /**
    265      * The listener for the group members list loader
    266      */
    267     private final LoaderManager.LoaderCallbacks<Cursor> mGroupMemberListLoaderListener =
    268             new LoaderCallbacks<Cursor>() {
    269 
    270         @Override
    271         public CursorLoader onCreateLoader(int id, Bundle args) {
    272             return GroupMemberLoader.constructLoaderForGroupDetailQuery(mContext, mGroupId);
    273         }
    274 
    275         @Override
    276         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    277             updateSize(data.getCount());
    278             mAdapter.setContactCursor(data);
    279             mMemberListView.setEmptyView(mEmptyView);
    280         }
    281 
    282         @Override
    283         public void onLoaderReset(Loader<Cursor> loader) {}
    284     };
    285 
    286     private void bindGroupMetaData(Cursor cursor) {
    287         cursor.moveToPosition(-1);
    288         if (cursor.moveToNext()) {
    289             mAccountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
    290             mDataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
    291             mGroupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID);
    292             mGroupName = cursor.getString(GroupMetaDataLoader.TITLE);
    293             mIsReadOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1;
    294             updateTitle(mGroupName);
    295             // Must call invalidate so that the option menu will get updated
    296             getActivity().invalidateOptionsMenu ();
    297 
    298             final String accountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
    299             final String dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
    300             updateAccountType(accountTypeString, dataSet);
    301         }
    302     }
    303 
    304     private void updateTitle(String title) {
    305         if (mGroupTitle != null) {
    306             mGroupTitle.setText(title);
    307         } else {
    308             mListener.onGroupTitleUpdated(title);
    309         }
    310     }
    311 
    312     /**
    313      * Display the count of the number of group members.
    314      * @param size of the group (can be -1 if no size could be determined)
    315      */
    316     private void updateSize(int size) {
    317         String groupSizeString;
    318         if (size == -1) {
    319             groupSizeString = null;
    320         } else {
    321             String groupSizeTemplateString = getResources().getQuantityString(
    322                     R.plurals.num_contacts_in_group, size);
    323             AccountType accountType = mAccountTypeManager.getAccountType(mAccountTypeString,
    324                     mDataSet);
    325             groupSizeString = String.format(groupSizeTemplateString, size,
    326                     accountType.getDisplayLabel(mContext));
    327         }
    328 
    329         if (mGroupSize != null) {
    330             mGroupSize.setText(groupSizeString);
    331         } else {
    332             mListener.onGroupSizeUpdated(groupSizeString);
    333         }
    334     }
    335 
    336     /**
    337      * Once the account type, group source action, and group source URI have been determined
    338      * (based on the result from the {@link Loader}), then we can display this to the user in 1 of
    339      * 2 ways depending on screen size and orientation: either as a button in the action bar or as
    340      * a button in a static header on the page.
    341      */
    342     private void updateAccountType(final String accountTypeString, final String dataSet) {
    343 
    344         // If the group action should be shown in the action bar, then pass the data to the
    345         // listener who will take care of setting up the view and click listener. There is nothing
    346         // else to be done by this {@link Fragment}.
    347         if (mShowGroupActionInActionBar) {
    348             mListener.onAccountTypeUpdated(accountTypeString, dataSet);
    349             return;
    350         }
    351 
    352         final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity());
    353         final AccountType accountType =
    354                 manager.getAccountType(accountTypeString, dataSet);
    355 
    356         // Otherwise, if the {@link Fragment} needs to create and setup the button, then first
    357         // verify that there is a valid action.
    358         if (!TextUtils.isEmpty(accountType.getViewGroupActivity())) {
    359             if (mGroupSourceView == null) {
    360                 mGroupSourceView = GroupDetailDisplayUtils.getNewGroupSourceView(mContext);
    361                 // Figure out how to add the view to the fragment.
    362                 // If there is a static header with a container for the group source view, insert
    363                 // the view there.
    364                 if (mGroupSourceViewContainer != null) {
    365                     mGroupSourceViewContainer.addView(mGroupSourceView);
    366                 }
    367             }
    368 
    369             // Rebind the data since this action can change if the loader returns updated data
    370             mGroupSourceView.setVisibility(View.VISIBLE);
    371             GroupDetailDisplayUtils.bindGroupSourceView(mContext, mGroupSourceView,
    372                     accountTypeString, dataSet);
    373             mGroupSourceView.setOnClickListener(new OnClickListener() {
    374                 @Override
    375                 public void onClick(View v) {
    376                     final Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupId);
    377                     final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    378                     intent.setClassName(accountType.syncAdapterPackageName,
    379                             accountType.getViewGroupActivity());
    380                     startActivity(intent);
    381                 }
    382             });
    383         } else if (mGroupSourceView != null) {
    384             mGroupSourceView.setVisibility(View.GONE);
    385         }
    386     }
    387 
    388     @Override
    389     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
    390             int totalItemCount) {
    391     }
    392 
    393     @Override
    394     public void onScrollStateChanged(AbsListView view, int scrollState) {
    395         if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
    396             mPhotoManager.pause();
    397         } else {
    398             mPhotoManager.resume();
    399         }
    400     }
    401 
    402     @Override
    403     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
    404         inflater.inflate(R.menu.view_group, menu);
    405     }
    406 
    407     public boolean isOptionsMenuChanged() {
    408         return mOptionsMenuGroupDeletable != isGroupDeletable() &&
    409                 mOptionsMenuGroupPresent != isGroupPresent();
    410     }
    411 
    412     public boolean isGroupDeletable() {
    413         return mGroupUri != null && !mIsReadOnly;
    414     }
    415 
    416     public boolean isGroupPresent() {
    417         return mGroupUri != null;
    418     }
    419 
    420     @Override
    421     public void onPrepareOptionsMenu(Menu menu) {
    422         mOptionsMenuGroupDeletable = isGroupDeletable() && isVisible();
    423         mOptionsMenuGroupPresent = isGroupPresent() && isVisible();
    424 
    425         final MenuItem editMenu = menu.findItem(R.id.menu_edit_group);
    426         editMenu.setVisible(mOptionsMenuGroupPresent);
    427 
    428         final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group);
    429         deleteMenu.setVisible(mOptionsMenuGroupDeletable);
    430     }
    431 
    432     @Override
    433     public boolean onOptionsItemSelected(MenuItem item) {
    434         switch (item.getItemId()) {
    435             case R.id.menu_edit_group: {
    436                 if (mListener != null) mListener.onEditRequested(mGroupUri);
    437                 break;
    438             }
    439             case R.id.menu_delete_group: {
    440                 GroupDeletionDialogFragment.show(getFragmentManager(), mGroupId, mGroupName,
    441                         mCloseActivityAfterDelete);
    442                 return true;
    443             }
    444         }
    445         return false;
    446     }
    447 
    448     public void closeActivityAfterDelete(boolean closeActivity) {
    449         mCloseActivityAfterDelete = closeActivity;
    450     }
    451 
    452     public long getGroupId() {
    453         return mGroupId;
    454     }
    455 }
    456