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