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