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