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