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