PeopleActivity.java revision 746316a47c9ab982ebcc03edbc636019e9e34f40
1/* 2 * Copyright (C) 2010 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.activities; 18 19import com.android.contacts.ContactLoader; 20import com.android.contacts.ContactSaveService; 21import com.android.contacts.ContactsActivity; 22import com.android.contacts.ContactsUtils; 23import com.android.contacts.R; 24import com.android.contacts.activities.ActionBarAdapter.TabState; 25import com.android.contacts.detail.ContactDetailFragment; 26import com.android.contacts.detail.ContactDetailLayoutController; 27import com.android.contacts.detail.ContactDetailUpdatesFragment; 28import com.android.contacts.detail.ContactLoaderFragment; 29import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener; 30import com.android.contacts.group.GroupBrowseListFragment; 31import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener; 32import com.android.contacts.group.GroupDetailFragment; 33import com.android.contacts.interactions.ContactDeletionInteraction; 34import com.android.contacts.interactions.ImportExportDialogFragment; 35import com.android.contacts.interactions.PhoneNumberInteraction; 36import com.android.contacts.list.ContactBrowseListFragment; 37import com.android.contacts.list.ContactEntryListFragment; 38import com.android.contacts.list.ContactListFilter; 39import com.android.contacts.list.ContactListFilterController; 40import com.android.contacts.list.ContactTileAdapter.DisplayType; 41import com.android.contacts.list.ContactTileFrequentFragment; 42import com.android.contacts.list.ContactTileListFragment; 43import com.android.contacts.list.ContactsIntentResolver; 44import com.android.contacts.list.ContactsRequest; 45import com.android.contacts.list.ContactsUnavailableFragment; 46import com.android.contacts.list.DefaultContactBrowseListFragment; 47import com.android.contacts.list.DirectoryListLoader; 48import com.android.contacts.list.OnContactBrowserActionListener; 49import com.android.contacts.list.OnContactsUnavailableActionListener; 50import com.android.contacts.list.ProviderStatusLoader; 51import com.android.contacts.list.ProviderStatusLoader.ProviderStatusListener; 52import com.android.contacts.model.AccountTypeManager; 53import com.android.contacts.model.AccountWithDataSet; 54import com.android.contacts.preference.ContactsPreferenceActivity; 55import com.android.contacts.preference.DisplayOptionsPreferenceFragment; 56import com.android.contacts.util.AccountFilterUtil; 57import com.android.contacts.util.AccountPromptUtils; 58import com.android.contacts.util.AccountSelectionUtil; 59import com.android.contacts.util.AccountsListAdapter; 60import com.android.contacts.util.AccountsListAdapter.AccountListFilter; 61import com.android.contacts.util.Constants; 62import com.android.contacts.util.DialogManager; 63import com.android.contacts.util.PhoneCapabilityTester; 64 65import android.app.Fragment; 66import android.app.FragmentManager; 67import android.app.FragmentTransaction; 68import android.content.ActivityNotFoundException; 69import android.content.ContentValues; 70import android.content.Intent; 71import android.net.Uri; 72import android.os.Bundle; 73import android.os.Handler; 74import android.os.Parcelable; 75import android.preference.PreferenceActivity; 76import android.provider.ContactsContract; 77import android.provider.ContactsContract.Contacts; 78import android.provider.ContactsContract.Intents; 79import android.provider.ContactsContract.ProviderStatus; 80import android.provider.Settings; 81import android.support.v13.app.FragmentPagerAdapter; 82import android.support.v4.view.PagerAdapter; 83import android.support.v4.view.ViewPager; 84import android.util.Log; 85import android.view.KeyEvent; 86import android.view.Menu; 87import android.view.MenuInflater; 88import android.view.MenuItem; 89import android.view.View; 90import android.view.View.OnClickListener; 91import android.widget.AdapterView; 92import android.widget.AdapterView.OnItemClickListener; 93import android.widget.ListPopupWindow; 94import android.widget.Toast; 95 96import java.util.ArrayList; 97import java.util.List; 98import java.util.concurrent.atomic.AtomicInteger; 99 100/** 101 * Displays a list to browse contacts. For xlarge screens, this also displays a detail-pane on 102 * the right. 103 */ 104public class PeopleActivity extends ContactsActivity 105 implements View.OnCreateContextMenuListener, ActionBarAdapter.Listener, 106 DialogManager.DialogShowingViewActivity, 107 ContactListFilterController.ContactListFilterListener, ProviderStatusListener { 108 109 private static final String TAG = "PeopleActivity"; 110 111 private static final int SUBACTIVITY_NEW_GROUP = 2; 112 private static final int SUBACTIVITY_EDIT_GROUP = 3; 113 private static final int SUBACTIVITY_ACCOUNT_FILTER = 4; 114 115 private final DialogManager mDialogManager = new DialogManager(this); 116 117 private ContactsIntentResolver mIntentResolver; 118 private ContactsRequest mRequest; 119 120 private ActionBarAdapter mActionBarAdapter; 121 122 private ContactDetailFragment mContactDetailFragment; 123 private ContactDetailUpdatesFragment mContactDetailUpdatesFragment; 124 125 private ContactLoaderFragment mContactDetailLoaderFragment; 126 private final ContactDetailLoaderFragmentListener mContactDetailLoaderFragmentListener = 127 new ContactDetailLoaderFragmentListener(); 128 129 private GroupDetailFragment mGroupDetailFragment; 130 private final GroupDetailFragmentListener mGroupDetailFragmentListener = 131 new GroupDetailFragmentListener(); 132 133 private ContactTileListFragment.Listener mFavoritesFragmentListener = 134 new StrequentContactListFragmentListener(); 135 136 private ContactListFilterController mContactListFilterController; 137 138 private ContactsUnavailableFragment mContactsUnavailableFragment; 139 private ProviderStatusLoader mProviderStatusLoader; 140 private int mProviderStatus = -1; 141 142 private boolean mOptionsMenuContactsAvailable; 143 144 /** 145 * Showing a list of Contacts. Also used for showing search results in search mode. 146 */ 147 private DefaultContactBrowseListFragment mAllFragment; 148 private ContactTileListFragment mFavoritesFragment; 149 private ContactTileFrequentFragment mFrequentFragment; 150 private GroupBrowseListFragment mGroupsFragment; 151 152 private View mFavoritesView; 153 private View mBrowserView; 154 private View mDetailsView; 155 156 private View mAddGroupImageView; 157 158 /** ViewPager for swipe, used only on the phone (i.e. one-pane mode) */ 159 private ViewPager mTabPager; 160 private TabPagerAdapter mTabPagerAdapter; 161 private final TabPagerListener mTabPagerListener = new TabPagerListener(); 162 163 private ContactDetailLayoutController mContactDetailLayoutController; 164 165 private final Handler mHandler = new Handler(); 166 167 /** 168 * True if this activity instance is a re-created one. i.e. set true after orientation change. 169 * This is set in {@link #onCreate} for later use in {@link #onStart}. 170 */ 171 private boolean mIsRecreatedInstance; 172 173 /** 174 * If {@link #configureFragments(boolean)} is already called. Used to avoid calling it twice 175 * in {@link #onStart}. 176 * (This initialization only needs to be done once in onStart() when the Activity was just 177 * created from scratch -- i.e. onCreate() was just called) 178 */ 179 private boolean mFragmentInitialized; 180 181 /** Sequential ID assigned to each instance; used for logging */ 182 private final int mInstanceId; 183 private static final AtomicInteger sNextInstanceId = new AtomicInteger(); 184 185 public PeopleActivity() { 186 mInstanceId = sNextInstanceId.getAndIncrement(); 187 mIntentResolver = new ContactsIntentResolver(this); 188 mProviderStatusLoader = new ProviderStatusLoader(this); 189 } 190 191 @Override 192 public String toString() { 193 // Shown on logcat 194 return String.format("%s@%d", getClass().getSimpleName(), mInstanceId); 195 } 196 197 public boolean areContactsAvailable() { 198 return mProviderStatus == ProviderStatus.STATUS_NORMAL; 199 } 200 201 private boolean areContactWritableAccountsAvailable() { 202 return ContactsUtils.areContactWritableAccountsAvailable(this); 203 } 204 205 private boolean areGroupWritableAccountsAvailable() { 206 return ContactsUtils.areGroupWritableAccountsAvailable(this); 207 } 208 209 /** 210 * Initialize fragments that are (or may not be) in the layout. 211 * 212 * For the fragments that are in the layout, we initialize them in 213 * {@link #createViewsAndFragments(Bundle)} after inflating the layout. 214 * 215 * However, there are special fragments which may not be in the layout, so we have to do the 216 * initialization here. 217 * The target fragments are: 218 * - {@link ContactDetailFragment} and {@link ContactDetailUpdatesFragment}: They may not be 219 * in the layout depending on the configuration. (i.e. portrait) 220 * - {@link ContactsUnavailableFragment}: We always create it at runtime. 221 */ 222 @Override 223 public void onAttachFragment(Fragment fragment) { 224 if (fragment instanceof ContactDetailFragment) { 225 mContactDetailFragment = (ContactDetailFragment) fragment; 226 } else if (fragment instanceof ContactDetailUpdatesFragment) { 227 mContactDetailUpdatesFragment = (ContactDetailUpdatesFragment) fragment; 228 } else if (fragment instanceof ContactsUnavailableFragment) { 229 mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment; 230 mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader); 231 mContactsUnavailableFragment.setOnContactsUnavailableActionListener( 232 new ContactsUnavailableFragmentListener()); 233 } 234 } 235 236 @Override 237 protected void onCreate(Bundle savedState) { 238 if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) { 239 Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start"); 240 } 241 super.onCreate(savedState); 242 243 if (!processIntent(false)) { 244 finish(); 245 return; 246 } 247 248 mContactListFilterController = ContactListFilterController.getInstance(this); 249 mContactListFilterController.addListener(this); 250 251 mIsRecreatedInstance = (savedState != null); 252 createViewsAndFragments(savedState); 253 if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) { 254 Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish"); 255 } 256 } 257 258 @Override 259 protected void onNewIntent(Intent intent) { 260 setIntent(intent); 261 if (!processIntent(true)) { 262 finish(); 263 return; 264 } 265 mActionBarAdapter.initialize(null, mRequest); 266 267 // Re-configure fragments. 268 configureFragments(true /* from request */); 269 invalidateOptionsMenuIfNeeded(); 270 } 271 272 /** 273 * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect 274 * is needed. 275 * 276 * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}. 277 * @return {@code true} if {@link PeopleActivity} should continue running. {@code false} 278 * if it shouldn't, in which case the caller should finish() itself and shouldn't do 279 * farther initialization. 280 */ 281 private boolean processIntent(boolean forNewIntent) { 282 // Extract relevant information from the intent 283 mRequest = mIntentResolver.resolveIntent(getIntent()); 284 if (Log.isLoggable(TAG, Log.DEBUG)) { 285 Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent 286 + " intent=" + getIntent() + " request=" + mRequest); 287 } 288 if (!mRequest.isValid()) { 289 setResult(RESULT_CANCELED); 290 return false; 291 } 292 293 Intent redirect = mRequest.getRedirectIntent(); 294 if (redirect != null) { 295 // Need to start a different activity 296 startActivity(redirect); 297 return false; 298 } 299 300 if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT 301 && !PhoneCapabilityTester.isUsingTwoPanes(this)) { 302 redirect = new Intent(this, ContactDetailActivity.class); 303 redirect.setAction(Intent.ACTION_VIEW); 304 redirect.setData(mRequest.getContactUri()); 305 startActivity(redirect); 306 return false; 307 } 308 setTitle(mRequest.getActivityTitle()); 309 return true; 310 } 311 312 private void createViewsAndFragments(Bundle savedState) { 313 setContentView(R.layout.people_activity); 314 315 final FragmentManager fragmentManager = getFragmentManager(); 316 317 // Hide all tabs (the current tab will later be reshown once a tab is selected) 318 final FragmentTransaction transaction = fragmentManager.beginTransaction(); 319 320 // Prepare the fragments which are used both on 1-pane and on 2-pane. 321 boolean isUsingTwoPanes = PhoneCapabilityTester.isUsingTwoPanes(this); 322 if (isUsingTwoPanes) { 323 mFavoritesFragment = getFragment(R.id.favorites_fragment); 324 mAllFragment = getFragment(R.id.all_fragment); 325 mGroupsFragment = getFragment(R.id.groups_fragment); 326 } else { 327 mTabPager = getView(R.id.tab_pager); 328 mTabPagerAdapter = new TabPagerAdapter(); 329 mTabPager.setAdapter(mTabPagerAdapter); 330 mTabPager.setOnPageChangeListener(mTabPagerListener); 331 332 final String FAVORITE_TAG = "tab-pager-favorite"; 333 final String ALL_TAG = "tab-pager-all"; 334 final String GROUPS_TAG = "tab-pager-groups"; 335 336 // Create the fragments and add as children of the view pager. 337 // The pager adapter will only change the visibility; it'll never create/destroy 338 // fragments. 339 // However, if it's after screen rotation, the fragments have been re-created by 340 // the fragment manager, so first see if there're already the target fragments 341 // existing. 342 mFavoritesFragment = (ContactTileListFragment) 343 fragmentManager.findFragmentByTag(FAVORITE_TAG); 344 mAllFragment = (DefaultContactBrowseListFragment) 345 fragmentManager.findFragmentByTag(ALL_TAG); 346 mGroupsFragment = (GroupBrowseListFragment) 347 fragmentManager.findFragmentByTag(GROUPS_TAG); 348 349 if (mFavoritesFragment == null) { 350 mFavoritesFragment = new ContactTileListFragment(); 351 mAllFragment = new DefaultContactBrowseListFragment(); 352 mGroupsFragment = new GroupBrowseListFragment(); 353 354 transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG); 355 transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG); 356 transaction.add(R.id.tab_pager, mGroupsFragment, GROUPS_TAG); 357 } 358 } 359 360 mFavoritesFragment.setListener(mFavoritesFragmentListener); 361 362 mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener()); 363 364 mGroupsFragment.setListener(new GroupBrowserActionListener()); 365 366 // Hide all fragments for now. We adjust visibility when we get onSelectedTabChanged() 367 // from ActionBarAdapter. 368 transaction.hide(mFavoritesFragment); 369 transaction.hide(mAllFragment); 370 transaction.hide(mGroupsFragment); 371 372 if (isUsingTwoPanes) { 373 // Prepare 2-pane only fragments/views... 374 375 // Container views for fragments 376 mFavoritesView = getView(R.id.favorites_view); 377 mDetailsView = getView(R.id.details_view); 378 mBrowserView = getView(R.id.browse_view); 379 380 // 2-pane only fragments 381 mFrequentFragment = getFragment(R.id.frequent_fragment); 382 mFrequentFragment.setListener(mFavoritesFragmentListener); 383 mFrequentFragment.setDisplayType(DisplayType.FREQUENT_ONLY); 384 mFrequentFragment.enableQuickContact(true); 385 386 mContactDetailLoaderFragment = getFragment(R.id.contact_detail_loader_fragment); 387 mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener); 388 389 mGroupDetailFragment = getFragment(R.id.group_detail_fragment); 390 mGroupDetailFragment.setListener(mGroupDetailFragmentListener); 391 mGroupDetailFragment.setQuickContact(true); 392 393 if (mContactDetailFragment != null) { 394 transaction.hide(mContactDetailFragment); 395 } 396 transaction.hide(mGroupDetailFragment); 397 398 // Configure contact details 399 mContactDetailLayoutController = new ContactDetailLayoutController(this, savedState, 400 getFragmentManager(), findViewById(R.id.contact_detail_container), 401 new ContactDetailFragmentListener()); 402 } 403 transaction.commitAllowingStateLoss(); 404 fragmentManager.executePendingTransactions(); 405 406 // Setting Properties after fragment is created 407 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 408 mFavoritesFragment.enableQuickContact(true); 409 mFavoritesFragment.setDisplayType(DisplayType.STARRED_ONLY); 410 } else { 411 mFavoritesFragment.setDisplayType(DisplayType.STREQUENT); 412 } 413 414 // Configure action bar 415 mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar(), isUsingTwoPanes); 416 mActionBarAdapter.initialize(savedState, mRequest); 417 418 invalidateOptionsMenuIfNeeded(); 419 } 420 421 @Override 422 protected void onStart() { 423 if (!mFragmentInitialized) { 424 mFragmentInitialized = true; 425 /* Configure fragments if we haven't. 426 * 427 * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}. 428 * 429 * However, because this method may indirectly touch views in fragments but fragments 430 * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT 431 * have views until {@link Activity#onCreate} finishes (they would if they were inflated 432 * from a layout), we need to do it here in {@link #onStart()}. 433 * 434 * (When {@link Fragment#onCreateView} is called is different in the former case and 435 * in the latter case, unfortunately.) 436 * 437 * Also, we skip most of the work in it if the activity is a re-created one. 438 * (so the argument.) 439 */ 440 configureFragments(!mIsRecreatedInstance); 441 } 442 super.onStart(); 443 } 444 445 @Override 446 protected void onPause() { 447 mOptionsMenuContactsAvailable = false; 448 449 mProviderStatus = -1; 450 mProviderStatusLoader.setProviderStatusListener(null); 451 super.onPause(); 452 } 453 454 @Override 455 protected void onResume() { 456 super.onResume(); 457 mProviderStatusLoader.setProviderStatusListener(this); 458 showContactsUnavailableFragmentIfNecessary(); 459 460 // Re-register the listener, which may have been cleared when onSaveInstanceState was 461 // called. See also: onSaveInstanceState 462 mActionBarAdapter.setListener(this); 463 if (mTabPager != null) { 464 mTabPager.setOnPageChangeListener(mTabPagerListener); 465 } 466 // Current tab may have changed since the last onSaveInstanceState(). Make sure 467 // the actual contents match the tab. 468 updateFragmentsVisibility(); 469 } 470 471 @Override 472 protected void onDestroy() { 473 // mActionBarAdapter will be null here when redirecting to another activity in 474 // configureContentView(). 475 if (mActionBarAdapter != null) { 476 mActionBarAdapter.setListener(null); 477 } 478 mContactListFilterController.removeListener(this); 479 super.onDestroy(); 480 } 481 482 private void configureFragments(boolean fromRequest) { 483 if (fromRequest) { 484 ContactListFilter filter = null; 485 int actionCode = mRequest.getActionCode(); 486 boolean searchMode = mRequest.isSearchMode(); 487 TabState tabToOpen = null; 488 switch (actionCode) { 489 case ContactsRequest.ACTION_ALL_CONTACTS: 490 filter = ContactListFilter.createFilterWithType( 491 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 492 tabToOpen = TabState.ALL; 493 break; 494 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES: 495 filter = ContactListFilter.createFilterWithType( 496 ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY); 497 tabToOpen = TabState.ALL; 498 break; 499 500 case ContactsRequest.ACTION_FREQUENT: 501 case ContactsRequest.ACTION_STREQUENT: 502 case ContactsRequest.ACTION_STARRED: 503 tabToOpen = TabState.FAVORITES; 504 break; 505 case ContactsRequest.ACTION_VIEW_CONTACT: 506 // We redirect this intent to the detail activity on 1-pane, so we don't get 507 // here. It's only for 2-pane. 508 tabToOpen = TabState.ALL; 509 break; 510 case ContactsRequest.ACTION_GROUP: 511 tabToOpen = TabState.GROUPS; 512 break; 513 } 514 if (tabToOpen != null) { 515 mActionBarAdapter.setCurrentTab(tabToOpen); 516 } 517 518 if (filter != null) { 519 mContactListFilterController.setContactListFilter(filter, false); 520 searchMode = false; 521 } 522 523 if (mRequest.getContactUri() != null) { 524 searchMode = false; 525 } 526 527 mActionBarAdapter.setSearchMode(searchMode); 528 configureContactListFragmentForRequest(); 529 } 530 531 configureContactListFragment(); 532 configureGroupListFragment(); 533 534 invalidateOptionsMenuIfNeeded(); 535 } 536 537 @Override 538 public void onContactListFilterChanged() { 539 if (mAllFragment == null || !mAllFragment.isAdded()) { 540 return; 541 } 542 543 mAllFragment.setFilter(mContactListFilterController.getFilter()); 544 545 invalidateOptionsMenuIfNeeded(); 546 } 547 548 private void setupContactDetailFragment(final Uri contactLookupUri) { 549 mContactDetailLoaderFragment.loadUri(contactLookupUri); 550 invalidateOptionsMenuIfNeeded(); 551 } 552 553 private void setupGroupDetailFragment(Uri groupUri) { 554 mGroupDetailFragment.loadGroup(groupUri); 555 invalidateOptionsMenuIfNeeded(); 556 } 557 558 /** 559 * Handler for action bar actions. 560 */ 561 @Override 562 public void onAction(Action action) { 563 switch (action) { 564 case START_SEARCH_MODE: 565 // Tell the fragments that we're in the search mode 566 configureFragments(false /* from request */); 567 updateFragmentsVisibility(); 568 invalidateOptionsMenu(); 569 break; 570 case STOP_SEARCH_MODE: 571 setQueryTextToFragment(""); 572 updateFragmentsVisibility(); 573 invalidateOptionsMenu(); 574 break; 575 case CHANGE_SEARCH_QUERY: 576 setQueryTextToFragment(mActionBarAdapter.getQueryString()); 577 break; 578 default: 579 throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action); 580 } 581 } 582 583 @Override 584 public void onSelectedTabChanged() { 585 updateFragmentsVisibility(); 586 } 587 588 /** 589 * Updates the fragment/view visibility according to the current mode, such as 590 * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}. 591 */ 592 private void updateFragmentsVisibility() { 593 TabState tab = mActionBarAdapter.getCurrentTab(); 594 595 // We use ViewPager on 1-pane. 596 if (!PhoneCapabilityTester.isUsingTwoPanes(this)) { 597 if (mActionBarAdapter.isSearchMode()) { 598 mTabPagerAdapter.setSearchMode(true); 599 } else { 600 // No smooth scrolling if quitting from the search mode. 601 final boolean wasSearchMode = mTabPagerAdapter.isSearchMode(); 602 mTabPagerAdapter.setSearchMode(false); 603 int tabIndex = tab.ordinal(); 604 if (mTabPager.getCurrentItem() != tabIndex) { 605 mTabPager.setCurrentItem(tabIndex, !wasSearchMode); 606 } 607 } 608 invalidateOptionsMenu(); 609 showEmptyStateForTab(tab); 610 if (tab == TabState.GROUPS) { 611 mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); 612 } 613 return; 614 } 615 616 // for the tablet... 617 618 // If in search mode, we use the all list + contact details to show the result. 619 if (mActionBarAdapter.isSearchMode()) { 620 tab = TabState.ALL; 621 } 622 switch (tab) { 623 case FAVORITES: 624 mFavoritesView.setVisibility(View.VISIBLE); 625 mBrowserView.setVisibility(View.GONE); 626 mDetailsView.setVisibility(View.GONE); 627 break; 628 case GROUPS: 629 mFavoritesView.setVisibility(View.GONE); 630 mBrowserView.setVisibility(View.VISIBLE); 631 mDetailsView.setVisibility(View.VISIBLE); 632 mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); 633 break; 634 case ALL: 635 mFavoritesView.setVisibility(View.GONE); 636 mBrowserView.setVisibility(View.VISIBLE); 637 mDetailsView.setVisibility(View.VISIBLE); 638 break; 639 } 640 FragmentManager fragmentManager = getFragmentManager(); 641 FragmentTransaction ft = fragmentManager.beginTransaction(); 642 643 // Note mContactDetailLoaderFragment is an invisible fragment, but we still have to show/ 644 // hide it so its options menu will be shown/hidden. 645 switch (tab) { 646 case FAVORITES: 647 showFragment(ft, mFavoritesFragment); 648 showFragment(ft, mFrequentFragment); 649 hideFragment(ft, mAllFragment); 650 hideFragment(ft, mContactDetailLoaderFragment); 651 hideFragment(ft, mContactDetailFragment); 652 hideFragment(ft, mGroupsFragment); 653 hideFragment(ft, mGroupDetailFragment); 654 break; 655 case ALL: 656 hideFragment(ft, mFavoritesFragment); 657 hideFragment(ft, mFrequentFragment); 658 showFragment(ft, mAllFragment); 659 showFragment(ft, mContactDetailLoaderFragment); 660 showFragment(ft, mContactDetailFragment); 661 hideFragment(ft, mGroupsFragment); 662 hideFragment(ft, mGroupDetailFragment); 663 break; 664 case GROUPS: 665 hideFragment(ft, mFavoritesFragment); 666 hideFragment(ft, mFrequentFragment); 667 hideFragment(ft, mAllFragment); 668 hideFragment(ft, mContactDetailLoaderFragment); 669 hideFragment(ft, mContactDetailFragment); 670 showFragment(ft, mGroupsFragment); 671 showFragment(ft, mGroupDetailFragment); 672 break; 673 } 674 if (!ft.isEmpty()) { 675 ft.commitAllowingStateLoss(); 676 fragmentManager.executePendingTransactions(); 677 // When switching tabs, we need to invalidate options menu, but executing a 678 // fragment transaction does it implicitly. We don't have to call invalidateOptionsMenu 679 // manually. 680 } 681 showEmptyStateForTab(tab); 682 } 683 684 private void showEmptyStateForTab(TabState tab) { 685 if (mContactsUnavailableFragment != null) { 686 switch (tab) { 687 case FAVORITES: 688 mContactsUnavailableFragment.setMessageText( 689 R.string.listTotalAllContactsZeroStarred, -1); 690 break; 691 case GROUPS: 692 mContactsUnavailableFragment.setMessageText(R.string.noGroups, 693 areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts); 694 break; 695 case ALL: 696 mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1); 697 break; 698 } 699 } 700 } 701 702 private class TabPagerListener implements ViewPager.OnPageChangeListener { 703 @Override 704 public void onPageScrollStateChanged(int state) { 705 } 706 707 @Override 708 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 709 } 710 711 @Override 712 public void onPageSelected(int position) { 713 // Make sure not in the search mode, in which case position != TabState.ordinal(). 714 if (!mTabPagerAdapter.isSearchMode()) { 715 TabState selectedTab = TabState.fromInt(position); 716 mActionBarAdapter.setCurrentTab(selectedTab, false); 717 showEmptyStateForTab(selectedTab); 718 if (selectedTab == TabState.GROUPS) { 719 mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); 720 } 721 invalidateOptionsMenu(); 722 } 723 } 724 } 725 726 /** 727 * Adapter for the {@link ViewPager}. Unlike {@link FragmentPagerAdapter}, 728 * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/ 729 * {@link #destroyItem} show/hide fragments instead of attaching/detaching. 730 * 731 * In search mode, we always show the "all" fragment, and disable the swipe. We change the 732 * number of items to 1 to disable the swipe. 733 * 734 * TODO figure out a more straight way to disable swipe. 735 */ 736 private class TabPagerAdapter extends PagerAdapter { 737 private final FragmentManager mFragmentManager; 738 private FragmentTransaction mCurTransaction = null; 739 740 private boolean mTabPagerAdapterSearchMode; 741 742 private Fragment mCurrentPrimaryItem; 743 744 public TabPagerAdapter() { 745 mFragmentManager = getFragmentManager(); 746 } 747 748 public boolean isSearchMode() { 749 return mTabPagerAdapterSearchMode; 750 } 751 752 public void setSearchMode(boolean searchMode) { 753 if (searchMode == mTabPagerAdapterSearchMode) { 754 return; 755 } 756 mTabPagerAdapterSearchMode = searchMode; 757 notifyDataSetChanged(); 758 } 759 760 @Override 761 public int getCount() { 762 return mTabPagerAdapterSearchMode ? 1 : TabState.values().length; 763 } 764 765 /** Gets called when the number of items changes. */ 766 @Override 767 public int getItemPosition(Object object) { 768 if (mTabPagerAdapterSearchMode) { 769 if (object == mAllFragment) { 770 return 0; // Only 1 page in search mode 771 } 772 } else { 773 if (object == mFavoritesFragment) { 774 return TabState.FAVORITES.ordinal(); 775 } 776 if (object == mAllFragment) { 777 return TabState.ALL.ordinal(); 778 } 779 if (object == mGroupsFragment) { 780 return TabState.GROUPS.ordinal(); 781 } 782 } 783 return POSITION_NONE; 784 } 785 786 @Override 787 public void startUpdate(View container) { 788 } 789 790 private Fragment getFragment(int position) { 791 if (mTabPagerAdapterSearchMode) { 792 if (position == 0) { 793 return mAllFragment; 794 } 795 } else { 796 if (position == TabState.FAVORITES.ordinal()) { 797 return mFavoritesFragment; 798 } else if (position == TabState.ALL.ordinal()) { 799 return mAllFragment; 800 } else if (position == TabState.GROUPS.ordinal()) { 801 return mGroupsFragment; 802 } 803 } 804 throw new IllegalArgumentException("position: " + position); 805 } 806 807 @Override 808 public Object instantiateItem(View container, int position) { 809 if (mCurTransaction == null) { 810 mCurTransaction = mFragmentManager.beginTransaction(); 811 } 812 Fragment f = getFragment(position); 813 mCurTransaction.show(f); 814 815 // Non primary pages should be deferred. 816 f.setStartDeferred(f != mCurrentPrimaryItem); 817 return f; 818 } 819 820 @Override 821 public void destroyItem(View container, int position, Object object) { 822 if (mCurTransaction == null) { 823 mCurTransaction = mFragmentManager.beginTransaction(); 824 } 825 mCurTransaction.hide((Fragment) object); 826 } 827 828 @Override 829 public void finishUpdate(View container) { 830 if (mCurTransaction != null) { 831 mCurTransaction.commitAllowingStateLoss(); 832 mCurTransaction = null; 833 mFragmentManager.executePendingTransactions(); 834 } 835 } 836 837 @Override 838 public boolean isViewFromObject(View view, Object object) { 839 return ((Fragment) object).getView() == view; 840 } 841 842 @Override 843 public void setPrimaryItem(View container, int position, Object object) { 844 Fragment fragment = (Fragment) object; 845 if (mCurrentPrimaryItem != fragment) { 846 if (mCurrentPrimaryItem != null) { 847 mCurrentPrimaryItem.setStartDeferred(true); 848 } 849 if (fragment != null) { 850 fragment.setStartDeferred(false); 851 } 852 mCurrentPrimaryItem = fragment; 853 } 854 } 855 856 @Override 857 public Parcelable saveState() { 858 return null; 859 } 860 861 @Override 862 public void restoreState(Parcelable state, ClassLoader loader) { 863 } 864 } 865 866 private void setQueryTextToFragment(String query) { 867 mAllFragment.setQueryString(query, true); 868 mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode()); 869 } 870 871 private void configureContactListFragmentForRequest() { 872 Uri contactUri = mRequest.getContactUri(); 873 if (contactUri != null) { 874 mAllFragment.setSelectedContactUri(contactUri); 875 } 876 877 mAllFragment.setFilter(mContactListFilterController.getFilter()); 878 setQueryTextToFragment(mActionBarAdapter.getQueryString()); 879 880 if (mRequest.isDirectorySearchEnabled()) { 881 mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT); 882 } else { 883 mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); 884 } 885 } 886 887 private void configureContactListFragment() { 888 // Filter may be changed when this Activity is in background. 889 mAllFragment.setFilter(mContactListFilterController.getFilter()); 890 891 final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); 892 mAllFragment.setVerticalScrollbarPosition( 893 useTwoPane 894 ? View.SCROLLBAR_POSITION_LEFT 895 : View.SCROLLBAR_POSITION_RIGHT); 896 mAllFragment.setSelectionVisible(useTwoPane); 897 mAllFragment.setQuickContactEnabled(!useTwoPane); 898 } 899 900 private void configureGroupListFragment() { 901 final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); 902 mGroupsFragment.setVerticalScrollbarPosition( 903 useTwoPane 904 ? View.SCROLLBAR_POSITION_LEFT 905 : View.SCROLLBAR_POSITION_RIGHT); 906 mGroupsFragment.setSelectionVisible(useTwoPane); 907 } 908 909 @Override 910 public void onProviderStatusChange() { 911 showContactsUnavailableFragmentIfNecessary(); 912 } 913 914 private void showContactsUnavailableFragmentIfNecessary() { 915 int providerStatus = mProviderStatusLoader.getProviderStatus(); 916 if (providerStatus == mProviderStatus) { 917 return; 918 } 919 920 mProviderStatus = providerStatus; 921 922 View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view); 923 View mainView = findViewById(R.id.main_view); 924 925 if (mProviderStatus == ProviderStatus.STATUS_NORMAL) { 926 contactsUnavailableView.setVisibility(View.GONE); 927 if (mainView != null) { 928 mainView.setVisibility(View.VISIBLE); 929 } 930 if (mAllFragment != null) { 931 mAllFragment.setEnabled(true); 932 } 933 } else { 934 // If there are no accounts on the device and we should show the "no account" prompt 935 // (based on {@link SharedPreferences}), then launch the account setup activity so the 936 // user can sign-in or create an account. 937 if (!areContactWritableAccountsAvailable() && 938 AccountPromptUtils.shouldShowAccountPrompt(this)) { 939 AccountPromptUtils.launchAccountPrompt(this); 940 return; 941 } 942 943 // Otherwise, continue setting up the page so that the user can still use the app 944 // without an account. 945 if (mAllFragment != null) { 946 mAllFragment.setEnabled(false); 947 } 948 if (mContactsUnavailableFragment == null) { 949 mContactsUnavailableFragment = new ContactsUnavailableFragment(); 950 mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader); 951 mContactsUnavailableFragment.setOnContactsUnavailableActionListener( 952 new ContactsUnavailableFragmentListener()); 953 getFragmentManager().beginTransaction() 954 .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment) 955 .commitAllowingStateLoss(); 956 } else { 957 mContactsUnavailableFragment.update(); 958 } 959 contactsUnavailableView.setVisibility(View.VISIBLE); 960 if (mainView != null) { 961 mainView.setVisibility(View.INVISIBLE); 962 } 963 964 TabState tab = mActionBarAdapter.getCurrentTab(); 965 showEmptyStateForTab(tab); 966 } 967 968 invalidateOptionsMenuIfNeeded(); 969 } 970 971 private final class ContactBrowserActionListener implements OnContactBrowserActionListener { 972 973 @Override 974 public void onSelectionChange() { 975 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 976 setupContactDetailFragment(mAllFragment.getSelectedContactUri()); 977 } 978 } 979 980 @Override 981 public void onViewContactAction(Uri contactLookupUri) { 982 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 983 setupContactDetailFragment(contactLookupUri); 984 } else { 985 Intent intent = new Intent(Intent.ACTION_VIEW, contactLookupUri); 986 // In search mode, the "up" affordance in the contact detail page should return the 987 // user to the search results, so suppress the normal behavior which would re-launch 988 // {@link PeopleActivity} when the "up" affordance is clicked. 989 if (mActionBarAdapter.isSearchMode()) { 990 intent.putExtra(ContactDetailActivity.INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR, 991 true); 992 } 993 startActivity(intent); 994 } 995 } 996 997 @Override 998 public void onCreateNewContactAction() { 999 Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 1000 Bundle extras = getIntent().getExtras(); 1001 if (extras != null) { 1002 intent.putExtras(extras); 1003 } 1004 startActivity(intent); 1005 } 1006 1007 @Override 1008 public void onEditContactAction(Uri contactLookupUri) { 1009 Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri); 1010 Bundle extras = getIntent().getExtras(); 1011 if (extras != null) { 1012 intent.putExtras(extras); 1013 } 1014 startActivity(intent); 1015 } 1016 1017 @Override 1018 public void onAddToFavoritesAction(Uri contactUri) { 1019 ContentValues values = new ContentValues(1); 1020 values.put(Contacts.STARRED, 1); 1021 getContentResolver().update(contactUri, values, null, null); 1022 } 1023 1024 @Override 1025 public void onRemoveFromFavoritesAction(Uri contactUri) { 1026 ContentValues values = new ContentValues(1); 1027 values.put(Contacts.STARRED, 0); 1028 getContentResolver().update(contactUri, values, null, null); 1029 } 1030 1031 @Override 1032 public void onCallContactAction(Uri contactUri) { 1033 PhoneNumberInteraction.startInteractionForPhoneCall(PeopleActivity.this, contactUri); 1034 } 1035 1036 @Override 1037 public void onSmsContactAction(Uri contactUri) { 1038 PhoneNumberInteraction.startInteractionForTextMessage(PeopleActivity.this, contactUri); 1039 } 1040 1041 @Override 1042 public void onDeleteContactAction(Uri contactUri) { 1043 ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); 1044 } 1045 1046 @Override 1047 public void onFinishAction() { 1048 onBackPressed(); 1049 } 1050 1051 @Override 1052 public void onInvalidSelection() { 1053 ContactListFilter filter; 1054 ContactListFilter currentFilter = mAllFragment.getFilter(); 1055 if (currentFilter != null 1056 && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 1057 filter = ContactListFilter.createFilterWithType( 1058 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 1059 mAllFragment.setFilter(filter); 1060 } else { 1061 filter = ContactListFilter.createFilterWithType( 1062 ContactListFilter.FILTER_TYPE_SINGLE_CONTACT); 1063 mAllFragment.setFilter(filter, false); 1064 } 1065 mContactListFilterController.setContactListFilter(filter, true); 1066 } 1067 } 1068 1069 private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener { 1070 @Override 1071 public void onContactNotFound() { 1072 // Nothing needs to be done here 1073 } 1074 1075 @Override 1076 public void onDetailsLoaded(final ContactLoader.Result result) { 1077 if (result == null) { 1078 // Nothing is loaded. Show empty state. 1079 mContactDetailLayoutController.showEmptyState(); 1080 return; 1081 } 1082 // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the 1083 // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler} 1084 // on the main thread to execute later. 1085 mHandler.post(new Runnable() { 1086 @Override 1087 public void run() { 1088 // If the activity is destroyed (or will be destroyed soon), don't update the UI 1089 if (isFinishing()) { 1090 return; 1091 } 1092 mContactDetailLayoutController.setContactData(result); 1093 } 1094 }); 1095 } 1096 1097 @Override 1098 public void onEditRequested(Uri contactLookupUri) { 1099 startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri)); 1100 } 1101 1102 @Override 1103 public void onDeleteRequested(Uri contactUri) { 1104 ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); 1105 } 1106 } 1107 1108 public class ContactDetailFragmentListener implements ContactDetailFragment.Listener { 1109 @Override 1110 public void onItemClicked(Intent intent) { 1111 if (intent == null) { 1112 return; 1113 } 1114 try { 1115 startActivity(intent); 1116 } catch (ActivityNotFoundException e) { 1117 Log.e(TAG, "No activity found for intent: " + intent); 1118 } 1119 } 1120 1121 @Override 1122 public void onCreateRawContactRequested(ArrayList<ContentValues> values, 1123 AccountWithDataSet account) { 1124 Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy, 1125 Toast.LENGTH_LONG).show(); 1126 Intent serviceIntent = ContactSaveService.createNewRawContactIntent( 1127 PeopleActivity.this, values, account, 1128 PeopleActivity.class, Intent.ACTION_VIEW); 1129 startService(serviceIntent); 1130 } 1131 } 1132 1133 private class ContactsUnavailableFragmentListener 1134 implements OnContactsUnavailableActionListener { 1135 1136 @Override 1137 public void onCreateNewContactAction() { 1138 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 1139 } 1140 1141 @Override 1142 public void onAddAccountAction() { 1143 Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); 1144 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1145 intent.putExtra(Settings.EXTRA_AUTHORITIES, 1146 new String[] { ContactsContract.AUTHORITY }); 1147 startActivity(intent); 1148 } 1149 1150 @Override 1151 public void onImportContactsFromFileAction() { 1152 AccountSelectionUtil.doImportFromSdCard(PeopleActivity.this, null); 1153 } 1154 1155 @Override 1156 public void onFreeInternalStorageAction() { 1157 startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)); 1158 } 1159 } 1160 1161 private final class StrequentContactListFragmentListener 1162 implements ContactTileListFragment.Listener { 1163 @Override 1164 public void onContactSelected(Uri contactUri) { 1165 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 1166 setupContactDetailFragment(contactUri); 1167 } else { 1168 startActivity(new Intent(Intent.ACTION_VIEW, contactUri)); 1169 } 1170 } 1171 } 1172 1173 private final class GroupBrowserActionListener implements OnGroupBrowserActionListener { 1174 1175 @Override 1176 public void onViewGroupAction(Uri groupUri) { 1177 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 1178 setupGroupDetailFragment(groupUri); 1179 } else { 1180 Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class); 1181 intent.setData(groupUri); 1182 startActivity(intent); 1183 } 1184 } 1185 } 1186 1187 private class GroupDetailFragmentListener implements GroupDetailFragment.Listener { 1188 @Override 1189 public void onGroupSizeUpdated(String size) { 1190 // Nothing needs to be done here because the size will be displayed in the detail 1191 // fragment 1192 } 1193 1194 @Override 1195 public void onGroupTitleUpdated(String title) { 1196 // Nothing needs to be done here because the title will be displayed in the detail 1197 // fragment 1198 } 1199 1200 @Override 1201 public void onAccountTypeUpdated(String accountTypeString, String dataSet) { 1202 // Nothing needs to be done here because the group source will be displayed in the 1203 // detail fragment 1204 } 1205 1206 @Override 1207 public void onEditRequested(Uri groupUri) { 1208 final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); 1209 intent.setData(groupUri); 1210 intent.setAction(Intent.ACTION_EDIT); 1211 startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP); 1212 } 1213 1214 @Override 1215 public void onContactSelected(Uri contactUri) { 1216 // Nothing needs to be done here because either quickcontact will be displayed 1217 // or activity will take care of selection 1218 } 1219 } 1220 1221 public void startActivityAndForwardResult(final Intent intent) { 1222 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 1223 1224 // Forward extras to the new activity 1225 Bundle extras = getIntent().getExtras(); 1226 if (extras != null) { 1227 intent.putExtras(extras); 1228 } 1229 startActivity(intent); 1230 finish(); 1231 } 1232 1233 @Override 1234 public boolean onCreateOptionsMenu(Menu menu) { 1235 if (!areContactsAvailable()) { 1236 // If contacts aren't available, hide all menu items. 1237 return false; 1238 } 1239 super.onCreateOptionsMenu(menu); 1240 1241 MenuInflater inflater = getMenuInflater(); 1242 inflater.inflate(R.menu.actions, menu); 1243 1244 // On narrow screens we specify a NEW group button in the {@link ActionBar}, so that 1245 // it can be in the overflow menu. On wide screens, we use a custom view because we need 1246 // its location for anchoring the account-selector popup. 1247 final MenuItem addGroup = menu.findItem(R.id.menu_custom_add_group); 1248 if (addGroup != null) { 1249 mAddGroupImageView = getLayoutInflater().inflate( 1250 R.layout.add_group_menu_item, null, false); 1251 mAddGroupImageView.setOnClickListener(new OnClickListener() { 1252 @Override 1253 public void onClick(View v) { 1254 createNewGroupWithAccountDisambiguation(); 1255 } 1256 }); 1257 addGroup.setActionView(mAddGroupImageView); 1258 } 1259 return true; 1260 } 1261 1262 private void invalidateOptionsMenuIfNeeded() { 1263 if (isOptionsMenuChanged()) { 1264 invalidateOptionsMenu(); 1265 } 1266 } 1267 1268 public boolean isOptionsMenuChanged() { 1269 if (mOptionsMenuContactsAvailable != areContactsAvailable()) { 1270 return true; 1271 } 1272 1273 if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) { 1274 return true; 1275 } 1276 1277 if (mContactDetailLoaderFragment != null && 1278 mContactDetailLoaderFragment.isOptionsMenuChanged()) { 1279 return true; 1280 } 1281 1282 if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) { 1283 return true; 1284 } 1285 1286 return false; 1287 } 1288 1289 @Override 1290 public boolean onPrepareOptionsMenu(Menu menu) { 1291 mOptionsMenuContactsAvailable = areContactsAvailable(); 1292 if (!mOptionsMenuContactsAvailable) { 1293 return false; 1294 } 1295 1296 final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact); 1297 final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter); 1298 1299 MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group); 1300 if (addGroupMenu == null) { 1301 addGroupMenu = menu.findItem(R.id.menu_custom_add_group); 1302 } 1303 1304 final boolean isSearchMode = mActionBarAdapter.isSearchMode(); 1305 if (isSearchMode) { 1306 addContactMenu.setVisible(false); 1307 addGroupMenu.setVisible(false); 1308 contactsFilterMenu.setVisible(false); 1309 } else { 1310 switch (mActionBarAdapter.getCurrentTab()) { 1311 case FAVORITES: 1312 addContactMenu.setVisible(false); 1313 addGroupMenu.setVisible(false); 1314 contactsFilterMenu.setVisible(false); 1315 break; 1316 case ALL: 1317 addContactMenu.setVisible(true); 1318 addGroupMenu.setVisible(false); 1319 contactsFilterMenu.setVisible(true); 1320 break; 1321 case GROUPS: 1322 // Do not display the "new group" button if no accounts are available 1323 if (areGroupWritableAccountsAvailable()) { 1324 addGroupMenu.setVisible(true); 1325 } else { 1326 addGroupMenu.setVisible(false); 1327 } 1328 addContactMenu.setVisible(false); 1329 contactsFilterMenu.setVisible(false); 1330 break; 1331 } 1332 } 1333 final boolean showMiscOptions = !isSearchMode; 1334 makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions); 1335 makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions); 1336 makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions); 1337 makeMenuItemVisible(menu, R.id.menu_settings, 1338 showMiscOptions && !ContactsPreferenceActivity.isEmpty(this)); 1339 1340 return true; 1341 } 1342 1343 private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) { 1344 MenuItem item =menu.findItem(itemId); 1345 if (item != null) { 1346 item.setVisible(visible); 1347 } 1348 } 1349 1350 @Override 1351 public boolean onOptionsItemSelected(MenuItem item) { 1352 switch (item.getItemId()) { 1353 case android.R.id.home: { 1354 // The home icon on the action bar is pressed 1355 if (mActionBarAdapter.isUpShowing()) { 1356 // "UP" icon press -- should be treated as "back". 1357 onBackPressed(); 1358 } 1359 return true; 1360 } 1361 case R.id.menu_settings: { 1362 final Intent intent = new Intent(this, ContactsPreferenceActivity.class); 1363 // as there is only one section right now, make sure it is selected 1364 // on small screens, this also hides the section selector 1365 // Due to b/5045558, this code unfortunately only works properly on phones 1366 boolean settingsAreMultiPane = getResources().getBoolean( 1367 com.android.internal.R.bool.preferences_prefer_dual_pane); 1368 if (!settingsAreMultiPane) { 1369 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, 1370 DisplayOptionsPreferenceFragment.class.getName()); 1371 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE, 1372 R.string.preference_displayOptions); 1373 } 1374 startActivity(intent); 1375 return true; 1376 } 1377 case R.id.menu_contacts_filter: { 1378 AccountFilterUtil.startAccountFilterActivityForResult(this, 1379 SUBACTIVITY_ACCOUNT_FILTER); 1380 return true; 1381 } 1382 case R.id.menu_search: { 1383 onSearchRequested(); 1384 return true; 1385 } 1386 case R.id.menu_add_contact: { 1387 final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 1388 startActivity(intent); 1389 return true; 1390 } 1391 case R.id.menu_add_group: { 1392 createNewGroupWithAccountDisambiguation(); 1393 return true; 1394 } 1395 case R.id.menu_import_export: { 1396 ImportExportDialogFragment.show(getFragmentManager()); 1397 return true; 1398 } 1399 case R.id.menu_accounts: { 1400 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); 1401 intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] { 1402 ContactsContract.AUTHORITY 1403 }); 1404 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1405 startActivity(intent); 1406 return true; 1407 } 1408 } 1409 return false; 1410 } 1411 1412 private void createNewGroupWithAccountDisambiguation() { 1413 final List<AccountWithDataSet> accounts = 1414 AccountTypeManager.getInstance(this).getAccounts(true); 1415 if (accounts.size() <= 1 || mAddGroupImageView == null) { 1416 // No account to choose or no control to anchor the popup-menu to 1417 // ==> just go straight to the editor which will disambig if necessary 1418 final Intent intent = new Intent(this, GroupEditorActivity.class); 1419 intent.setAction(Intent.ACTION_INSERT); 1420 startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); 1421 return; 1422 } 1423 1424 final ListPopupWindow popup = new ListPopupWindow(this, null); 1425 popup.setWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width)); 1426 popup.setAnchorView(mAddGroupImageView); 1427 // Create a list adapter with all writeable accounts (assume that the writeable accounts all 1428 // allow group creation). 1429 final AccountsListAdapter adapter = new AccountsListAdapter(this, 1430 AccountListFilter.ACCOUNTS_GROUP_WRITABLE); 1431 popup.setAdapter(adapter); 1432 popup.setOnItemClickListener(new OnItemClickListener() { 1433 @Override 1434 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1435 popup.dismiss(); 1436 AccountWithDataSet account = adapter.getItem(position); 1437 final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); 1438 intent.setAction(Intent.ACTION_INSERT); 1439 intent.putExtra(Intents.Insert.ACCOUNT, account); 1440 intent.putExtra(Intents.Insert.DATA_SET, account.dataSet); 1441 startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); 1442 } 1443 }); 1444 popup.setModal(true); 1445 popup.show(); 1446 } 1447 1448 @Override 1449 public boolean onSearchRequested() { // Search key pressed. 1450 mActionBarAdapter.setSearchMode(true); 1451 return true; 1452 } 1453 1454 @Override 1455 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1456 switch (requestCode) { 1457 case SUBACTIVITY_ACCOUNT_FILTER: { 1458 AccountFilterUtil.handleAccountFilterResult( 1459 mContactListFilterController, resultCode, data); 1460 break; 1461 } 1462 1463 case SUBACTIVITY_NEW_GROUP: 1464 case SUBACTIVITY_EDIT_GROUP: { 1465 if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) { 1466 mRequest.setActionCode(ContactsRequest.ACTION_GROUP); 1467 mGroupsFragment.setSelectedUri(data.getData()); 1468 } 1469 break; 1470 } 1471 1472 // TODO: Using the new startActivityWithResultFromFragment API this should not be needed 1473 // anymore 1474 case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER: 1475 if (resultCode == RESULT_OK) { 1476 mAllFragment.onPickerResult(data); 1477 } 1478 1479// TODO fix or remove multipicker code 1480// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) { 1481// // Finish the activity if the sub activity was canceled as back key is used 1482// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES. 1483// finish(); 1484// } 1485// break; 1486 } 1487 } 1488 1489 @Override 1490 public boolean onKeyDown(int keyCode, KeyEvent event) { 1491 // TODO move to the fragment 1492 switch (keyCode) { 1493// case KeyEvent.KEYCODE_CALL: { 1494// if (callSelection()) { 1495// return true; 1496// } 1497// break; 1498// } 1499 1500 case KeyEvent.KEYCODE_DEL: { 1501 if (deleteSelection()) { 1502 return true; 1503 } 1504 break; 1505 } 1506 default: { 1507 // Bring up the search UI if the user starts typing 1508 final int unicodeChar = event.getUnicodeChar(); 1509 if (unicodeChar != 0 && !Character.isWhitespace(unicodeChar)) { 1510 String query = new String(new int[]{ unicodeChar }, 0, 1); 1511 if (!mActionBarAdapter.isSearchMode()) { 1512 mActionBarAdapter.setQueryString(query); 1513 mActionBarAdapter.setSearchMode(true); 1514 return true; 1515 } 1516 } 1517 } 1518 } 1519 1520 return super.onKeyDown(keyCode, event); 1521 } 1522 1523 @Override 1524 public void onBackPressed() { 1525 if (mActionBarAdapter.isSearchMode()) { 1526 mActionBarAdapter.setSearchMode(false); 1527 } else { 1528 super.onBackPressed(); 1529 } 1530 } 1531 1532 private boolean deleteSelection() { 1533 // TODO move to the fragment 1534// if (mActionCode == ContactsRequest.ACTION_DEFAULT) { 1535// final int position = mListView.getSelectedItemPosition(); 1536// if (position != ListView.INVALID_POSITION) { 1537// Uri contactUri = getContactUri(position); 1538// if (contactUri != null) { 1539// doContactDelete(contactUri); 1540// return true; 1541// } 1542// } 1543// } 1544 return false; 1545 } 1546 1547 @Override 1548 protected void onSaveInstanceState(Bundle outState) { 1549 super.onSaveInstanceState(outState); 1550 mActionBarAdapter.onSaveInstanceState(outState); 1551 if (mContactDetailLayoutController != null) { 1552 mContactDetailLayoutController.onSaveInstanceState(outState); 1553 } 1554 1555 // Clear the listener to make sure we don't get callbacks after onSaveInstanceState, 1556 // in order to avoid doing fragment transactions after it. 1557 // TODO Figure out a better way to deal with the issue. 1558 mActionBarAdapter.setListener(null); 1559 if (mTabPager != null) { 1560 mTabPager.setOnPageChangeListener(null); 1561 } 1562 } 1563 1564 @Override 1565 public DialogManager getDialogManager() { 1566 return mDialogManager; 1567 } 1568 1569 // Visible for testing 1570 public ContactBrowseListFragment getListFragment() { 1571 return mAllFragment; 1572 } 1573 1574 // Visible for testing 1575 public ContactDetailFragment getDetailFragment() { 1576 return mContactDetailFragment; 1577 } 1578} 1579