• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
onGroupTitleUpdated(String title)68         public void onGroupTitleUpdated(String title);
69 
70         /**
71          * The number of group members has been determined
72          */
onGroupSizeUpdated(String size)73         public void onGroupSizeUpdated(String size);
74 
75         /**
76          * The account type and dataset have been determined.
77          */
onAccountTypeUpdated(String accountTypeString, String dataSet)78         public void onAccountTypeUpdated(String accountTypeString, String dataSet);
79 
80         /**
81          * User decided to go to Edit-Mode
82          */
onEditRequested(Uri groupUri)83         public void onEditRequested(Uri groupUri);
84 
85         /**
86          * Contact is selected and should launch details page
87          */
onContactSelected(Uri contactUri)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 
GroupDetailFragment()125     public GroupDetailFragment() {
126     }
127 
128     @Override
onAttach(Activity activity)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
onDetach()143     public void onDetach() {
144         super.onDetach();
145         mContext = null;
146     }
147 
148     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)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 
loadGroup(Uri groupUri)164     public void loadGroup(Uri groupUri) {
165         mGroupUri= groupUri;
166         startGroupMetadataLoader();
167     }
168 
setQuickContact(boolean enableQuickContact)169     public void setQuickContact(boolean enableQuickContact) {
170         mAdapter.enableQuickContact(enableQuickContact);
171     }
172 
configurePhotoLoader()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 
setListener(Listener value)187     public void setListener(Listener value) {
188         mListener = value;
189     }
190 
setShowGroupSourceInActionBar(boolean show)191     public void setShowGroupSourceInActionBar(boolean show) {
192         mShowGroupActionInActionBar = show;
193     }
194 
getGroupUri()195     public Uri getGroupUri() {
196         return mGroupUri;
197     }
198 
199     /**
200      * Start the loader to retrieve the metadata for this group.
201      */
startGroupMetadataLoader()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      */
startGroupMembersLoader()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 
bindGroupMetaData(Cursor cursor)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 
updateTitle(String title)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      */
updateSize(int size)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      */
updateAccountType(final String accountTypeString, final String dataSet)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
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)393     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
394             int totalItemCount) {
395     }
396 
397     @Override
onScrollStateChanged(AbsListView view, int scrollState)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
onCreateOptionsMenu(Menu menu, final MenuInflater inflater)407     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
408         inflater.inflate(R.menu.view_group, menu);
409     }
410 
isOptionsMenuChanged()411     public boolean isOptionsMenuChanged() {
412         return mOptionsMenuGroupDeletable != isGroupDeletable() &&
413                 mOptionsMenuGroupEditable != isGroupEditableAndPresent();
414     }
415 
isGroupDeletable()416     public boolean isGroupDeletable() {
417         return mGroupUri != null && !mIsReadOnly;
418     }
419 
isGroupEditableAndPresent()420     public boolean isGroupEditableAndPresent() {
421         return mGroupUri != null && mIsMembershipEditable;
422     }
423 
424     @Override
onPrepareOptionsMenu(Menu menu)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
onOptionsItemSelected(MenuItem item)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 
closeActivityAfterDelete(boolean closeActivity)452     public void closeActivityAfterDelete(boolean closeActivity) {
453         mCloseActivityAfterDelete = closeActivity;
454     }
455 
getGroupId()456     public long getGroupId() {
457         return mGroupId;
458     }
459 }
460