/* * Copyright (C) 2011 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.Activity; import android.app.Fragment; import android.app.LoaderManager; import android.app.LoaderManager.LoaderCallbacks; import android.content.ActivityNotFoundException; import android.content.ContentUris; import android.content.Context; import android.content.CursorLoader; import android.content.Intent; import android.content.Loader; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract.Groups; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.android.contacts.GroupMemberLoader; import com.android.contacts.GroupMetaDataLoader; import com.android.contacts.R; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.util.ImplicitIntentsUtil; import com.android.contacts.interactions.GroupDeletionDialogFragment; import com.android.contacts.common.list.ContactTileAdapter; import com.android.contacts.common.list.ContactTileView; import com.android.contacts.list.GroupMemberTileAdapter; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountType; /** * Displays the details of a group and shows a list of actions possible for the group. */ public class GroupDetailFragment extends Fragment implements OnScrollListener { public static interface Listener { /** * The group title has been loaded */ public void onGroupTitleUpdated(String title); /** * The number of group members has been determined */ public void onGroupSizeUpdated(String size); /** * The account type and dataset have been determined. */ public void onAccountTypeUpdated(String accountTypeString, String dataSet); /** * User decided to go to Edit-Mode */ public void onEditRequested(Uri groupUri); /** * Contact is selected and should launch details page */ public void onContactSelected(Uri contactUri); } private static final String TAG = "GroupDetailFragment"; private static final int LOADER_METADATA = 0; private static final int LOADER_MEMBERS = 1; private Context mContext; private View mRootView; private ViewGroup mGroupSourceViewContainer; private View mGroupSourceView; private TextView mGroupTitle; private TextView mGroupSize; private ListView mMemberListView; private View mEmptyView; private Listener mListener; private ContactTileAdapter mAdapter; private ContactPhotoManager mPhotoManager; private AccountTypeManager mAccountTypeManager; private Uri mGroupUri; private long mGroupId; private String mGroupName; private String mAccountTypeString; private String mDataSet; private boolean mIsReadOnly; private boolean mIsMembershipEditable; private boolean mShowGroupActionInActionBar; private boolean mOptionsMenuGroupDeletable; private boolean mOptionsMenuGroupEditable; private boolean mCloseActivityAfterDelete; public GroupDetailFragment() { } @Override public void onAttach(Activity activity) { super.onAttach(activity); mContext = activity; mAccountTypeManager = AccountTypeManager.getInstance(mContext); Resources res = getResources(); int columnCount = res.getInteger(R.integer.contact_tile_column_count); mAdapter = new GroupMemberTileAdapter(activity, mContactTileListener, columnCount); configurePhotoLoader(); } @Override public void onDetach() { super.onDetach(); mContext = null; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { setHasOptionsMenu(true); mRootView = inflater.inflate(R.layout.group_detail_fragment, container, false); mGroupTitle = (TextView) mRootView.findViewById(R.id.group_title); mGroupSize = (TextView) mRootView.findViewById(R.id.group_size); mGroupSourceViewContainer = (ViewGroup) mRootView.findViewById( R.id.group_source_view_container); mEmptyView = mRootView.findViewById(android.R.id.empty); mMemberListView = (ListView) mRootView.findViewById(android.R.id.list); mMemberListView.setItemsCanFocus(true); mMemberListView.setAdapter(mAdapter); return mRootView; } public void loadGroup(Uri groupUri) { mGroupUri= groupUri; startGroupMetadataLoader(); } public void setQuickContact(boolean enableQuickContact) { mAdapter.enableQuickContact(enableQuickContact); } private void configurePhotoLoader() { if (mContext != null) { if (mPhotoManager == null) { mPhotoManager = ContactPhotoManager.getInstance(mContext); } if (mMemberListView != null) { mMemberListView.setOnScrollListener(this); } if (mAdapter != null) { mAdapter.setPhotoLoader(mPhotoManager); } } } public void setListener(Listener value) { mListener = value; } public void setShowGroupSourceInActionBar(boolean show) { mShowGroupActionInActionBar = show; } public Uri getGroupUri() { return mGroupUri; } /** * Start the loader to retrieve the metadata for this group. */ private void startGroupMetadataLoader() { getLoaderManager().restartLoader(LOADER_METADATA, null, mGroupMetadataLoaderListener); } /** * Start the loader to retrieve the list of group members. */ private void startGroupMembersLoader() { getLoaderManager().restartLoader(LOADER_MEMBERS, null, mGroupMemberListLoaderListener); } private final ContactTileView.Listener mContactTileListener = new ContactTileView.Listener() { @Override public void onContactSelected(Uri contactUri, Rect targetRect) { mListener.onContactSelected(contactUri); } @Override public void onCallNumberDirectly(String phoneNumber) { // No need to call phone number directly from People app. Log.w(TAG, "unexpected invocation of onCallNumberDirectly()"); } @Override public int getApproximateTileWidth() { return getView().getWidth() / mAdapter.getColumnCount(); } }; /** * The listener for the group metadata loader. */ private final LoaderManager.LoaderCallbacks mGroupMetadataLoaderListener = new LoaderCallbacks() { @Override public CursorLoader onCreateLoader(int id, Bundle args) { return new GroupMetaDataLoader(mContext, mGroupUri); } @Override public void onLoadFinished(Loader loader, Cursor data) { if (data == null || data.isClosed()) { Log.e(TAG, "Failed to load group metadata"); return; } data.moveToPosition(-1); if (data.moveToNext()) { boolean deleted = data.getInt(GroupMetaDataLoader.DELETED) == 1; if (!deleted) { bindGroupMetaData(data); // Retrieve the list of members startGroupMembersLoader(); return; } } updateSize(-1); updateTitle(null); } @Override public void onLoaderReset(Loader loader) {} }; /** * The listener for the group members list loader */ private final LoaderManager.LoaderCallbacks mGroupMemberListLoaderListener = new LoaderCallbacks() { @Override public CursorLoader onCreateLoader(int id, Bundle args) { return GroupMemberLoader.constructLoaderForGroupDetailQuery(mContext, mGroupId); } @Override public void onLoadFinished(Loader loader, Cursor data) { if (data == null || data.isClosed()) { Log.e(TAG, "Failed to load group members"); return; } updateSize(data.getCount()); mAdapter.setContactCursor(data); mMemberListView.setEmptyView(mEmptyView); } @Override public void onLoaderReset(Loader loader) {} }; private void bindGroupMetaData(Cursor cursor) { cursor.moveToPosition(-1); if (cursor.moveToNext()) { mAccountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE); mDataSet = cursor.getString(GroupMetaDataLoader.DATA_SET); mGroupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID); mGroupName = cursor.getString(GroupMetaDataLoader.TITLE); mIsReadOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1; updateTitle(mGroupName); // Must call invalidate so that the option menu will get updated getActivity().invalidateOptionsMenu (); final String accountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE); final String dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET); updateAccountType(accountTypeString, dataSet); } } private void updateTitle(String title) { if (mGroupTitle != null) { mGroupTitle.setText(title); } else { mListener.onGroupTitleUpdated(title); } } /** * Display the count of the number of group members. * @param size of the group (can be -1 if no size could be determined) */ private void updateSize(int size) { String groupSizeString; if (size == -1) { groupSizeString = null; } else { AccountType accountType = mAccountTypeManager.getAccountType(mAccountTypeString, mDataSet); final CharSequence dispLabel = accountType.getDisplayLabel(mContext); if (!TextUtils.isEmpty(dispLabel)) { String groupSizeTemplateString = getResources().getQuantityString( R.plurals.num_contacts_in_group, size); groupSizeString = String.format(groupSizeTemplateString, size, dispLabel); } else { String groupSizeTemplateString = getResources().getQuantityString( R.plurals.group_list_num_contacts_in_group, size); groupSizeString = String.format(groupSizeTemplateString, size); } } if (mGroupSize != null) { mGroupSize.setText(groupSizeString); } else { mListener.onGroupSizeUpdated(groupSizeString); } } /** * Once the account type, group source action, and group source URI have been determined * (based on the result from the {@link Loader}), then we can display this to the user in 1 of * 2 ways depending on screen size and orientation: either as a button in the action bar or as * a button in a static header on the page. * We also use isGroupMembershipEditable() of accountType to determine whether or not we should * display the Edit option in the Actionbar. */ private void updateAccountType(final String accountTypeString, final String dataSet) { final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity()); final AccountType accountType = manager.getAccountType(accountTypeString, dataSet); mIsMembershipEditable = accountType.isGroupMembershipEditable(); // If the group action should be shown in the action bar, then pass the data to the // listener who will take care of setting up the view and click listener. There is nothing // else to be done by this {@link Fragment}. if (mShowGroupActionInActionBar) { mListener.onAccountTypeUpdated(accountTypeString, dataSet); return; } // Otherwise, if the {@link Fragment} needs to create and setup the button, then first // verify that there is a valid action. if (!TextUtils.isEmpty(accountType.getViewGroupActivity())) { if (mGroupSourceView == null) { mGroupSourceView = GroupDetailDisplayUtils.getNewGroupSourceView(mContext); // Figure out how to add the view to the fragment. // If there is a static header with a container for the group source view, insert // the view there. if (mGroupSourceViewContainer != null) { mGroupSourceViewContainer.addView(mGroupSourceView); } } // Rebind the data since this action can change if the loader returns updated data mGroupSourceView.setVisibility(View.VISIBLE); GroupDetailDisplayUtils.bindGroupSourceView(mContext, mGroupSourceView, accountTypeString, dataSet); mGroupSourceView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupId); final Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.setClassName(accountType.syncAdapterPackageName, accountType.getViewGroupActivity()); try { ImplicitIntentsUtil.startActivityInApp(getActivity(), intent); } catch (ActivityNotFoundException e) { Log.e(TAG, "startActivity() failed: " + e); Toast.makeText(getActivity(), R.string.missing_app, Toast.LENGTH_SHORT).show(); } } }); } else if (mGroupSourceView != null) { mGroupSourceView.setVisibility(View.GONE); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == OnScrollListener.SCROLL_STATE_FLING) { mPhotoManager.pause(); } else { mPhotoManager.resume(); } } @Override public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.view_group, menu); } public boolean isOptionsMenuChanged() { return mOptionsMenuGroupDeletable != isGroupDeletable() && mOptionsMenuGroupEditable != isGroupEditableAndPresent(); } public boolean isGroupDeletable() { return mGroupUri != null && !mIsReadOnly; } public boolean isGroupEditableAndPresent() { return mGroupUri != null && mIsMembershipEditable; } @Override public void onPrepareOptionsMenu(Menu menu) { mOptionsMenuGroupDeletable = isGroupDeletable() && isVisible(); mOptionsMenuGroupEditable = isGroupEditableAndPresent() && isVisible(); final MenuItem editMenu = menu.findItem(R.id.menu_edit_group); editMenu.setVisible(mOptionsMenuGroupEditable); final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group); deleteMenu.setVisible(mOptionsMenuGroupDeletable); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_edit_group: { if (mListener != null) mListener.onEditRequested(mGroupUri); break; } case R.id.menu_delete_group: { GroupDeletionDialogFragment.show(getFragmentManager(), mGroupId, mGroupName, mCloseActivityAfterDelete); return true; } } return false; } public void closeActivityAfterDelete(boolean closeActivity) { mCloseActivityAfterDelete = closeActivity; } public long getGroupId() { return mGroupId; } }