PeopleActivity.java revision 8f59c2b18b73df6af3c808802620839f0a6a61d4
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 searchMode = mActionBarAdapter.isSearchMode(); 871 mAllFragment.setSearchMode(searchMode); 872 873 final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); 874 mAllFragment.setVisibleScrollbarEnabled(!searchMode); 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 ContactListFilter filter; 1028 ContactListFilter currentFilter = mAllFragment.getFilter(); 1029 if (currentFilter != null 1030 && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 1031 filter = ContactListFilter.createFilterWithType( 1032 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 1033 mAllFragment.setFilter(filter); 1034 } else { 1035 filter = ContactListFilter.createFilterWithType( 1036 ContactListFilter.FILTER_TYPE_SINGLE_CONTACT); 1037 mAllFragment.setFilter(filter, false); 1038 } 1039 mContactListFilterController.setContactListFilter(filter, true); 1040 } 1041 } 1042 1043 private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener { 1044 @Override 1045 public void onContactNotFound() { 1046 // Nothing needs to be done here 1047 } 1048 1049 @Override 1050 public void onDetailsLoaded(final ContactLoader.Result result) { 1051 if (result == null) { 1052 return; 1053 } 1054 // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the 1055 // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler} 1056 // on the main thread to execute later. 1057 mHandler.post(new Runnable() { 1058 @Override 1059 public void run() { 1060 // If the activity is destroyed (or will be destroyed soon), don't update the UI 1061 if (isFinishing()) { 1062 return; 1063 } 1064 mContactDetailLayoutController.setContactData(result); 1065 } 1066 }); 1067 } 1068 1069 @Override 1070 public void onEditRequested(Uri contactLookupUri) { 1071 startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri)); 1072 } 1073 1074 @Override 1075 public void onDeleteRequested(Uri contactUri) { 1076 ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); 1077 } 1078 } 1079 1080 public class ContactDetailFragmentListener implements ContactDetailFragment.Listener { 1081 @Override 1082 public void onItemClicked(Intent intent) { 1083 try { 1084 startActivity(intent); 1085 } catch (ActivityNotFoundException e) { 1086 Log.e(TAG, "No activity found for intent: " + intent); 1087 } 1088 } 1089 1090 @Override 1091 public void onCreateRawContactRequested(ArrayList<ContentValues> values, 1092 AccountWithDataSet account) { 1093 Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy, 1094 Toast.LENGTH_LONG).show(); 1095 Intent serviceIntent = ContactSaveService.createNewRawContactIntent( 1096 PeopleActivity.this, values, account, 1097 PeopleActivity.class, Intent.ACTION_VIEW); 1098 startService(serviceIntent); 1099 } 1100 } 1101 1102 private class ContactsUnavailableFragmentListener 1103 implements OnContactsUnavailableActionListener { 1104 1105 @Override 1106 public void onCreateNewContactAction() { 1107 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 1108 } 1109 1110 @Override 1111 public void onAddAccountAction() { 1112 Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); 1113 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1114 intent.putExtra(Settings.EXTRA_AUTHORITIES, 1115 new String[] { ContactsContract.AUTHORITY }); 1116 startActivity(intent); 1117 } 1118 1119 @Override 1120 public void onImportContactsFromFileAction() { 1121 AccountSelectionUtil.doImportFromSdCard(PeopleActivity.this, null); 1122 } 1123 1124 @Override 1125 public void onFreeInternalStorageAction() { 1126 startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)); 1127 } 1128 } 1129 1130 private final class StrequentContactListFragmentListener 1131 implements ContactTileListFragment.Listener { 1132 @Override 1133 public void onContactSelected(Uri contactUri) { 1134 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 1135 setupContactDetailFragment(contactUri); 1136 } else { 1137 startActivity(new Intent(Intent.ACTION_VIEW, contactUri)); 1138 } 1139 } 1140 } 1141 1142 private final class GroupBrowserActionListener implements OnGroupBrowserActionListener { 1143 1144 @Override 1145 public void onViewGroupAction(Uri groupUri) { 1146 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 1147 setupGroupDetailFragment(groupUri); 1148 } else { 1149 Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class); 1150 intent.setData(groupUri); 1151 startActivity(intent); 1152 } 1153 } 1154 } 1155 1156 private class GroupDetailFragmentListener implements GroupDetailFragment.Listener { 1157 @Override 1158 public void onGroupSizeUpdated(String size) { 1159 // Nothing needs to be done here because the size will be displayed in the detail 1160 // fragment 1161 } 1162 1163 @Override 1164 public void onGroupTitleUpdated(String title) { 1165 // Nothing needs to be done here because the title will be displayed in the detail 1166 // fragment 1167 } 1168 1169 @Override 1170 public void onGroupSourceUpdated(String accountTypeString, String dataSet, 1171 String groupSourceAction, String groupSourceUri) { 1172 // Nothing needs to be done here because the group source will be displayed in the 1173 // detail fragment 1174 } 1175 1176 @Override 1177 public void onEditRequested(Uri groupUri) { 1178 final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); 1179 intent.setData(groupUri); 1180 intent.setAction(Intent.ACTION_EDIT); 1181 startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP); 1182 } 1183 1184 @Override 1185 public void onContactSelected(Uri contactUri) { 1186 // Nothing needs to be done here because either quickcontact will be displayed 1187 // or activity will take care of selection 1188 } 1189 } 1190 1191 public void startActivityAndForwardResult(final Intent intent) { 1192 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 1193 1194 // Forward extras to the new activity 1195 Bundle extras = getIntent().getExtras(); 1196 if (extras != null) { 1197 intent.putExtras(extras); 1198 } 1199 startActivity(intent); 1200 finish(); 1201 } 1202 1203 @Override 1204 public boolean onCreateOptionsMenu(Menu menu) { 1205// STOPSHIP Un-comment it once b/5027071 is fixed. 1206// if (!areContactsAvailable()) { 1207// If contacts aren't available, hide all menu items. 1208// return false; 1209// } 1210 super.onCreateOptionsMenu(menu); 1211 1212 MenuInflater inflater = getMenuInflater(); 1213 inflater.inflate(R.menu.actions, menu); 1214 1215 // On narrow screens we specify a NEW group button in the {@link ActionBar}, so that 1216 // it can be in the overflow menu. On wide screens, we use a custom view because we need 1217 // its location for anchoring the account-selector popup. 1218 final MenuItem addGroup = menu.findItem(R.id.menu_custom_add_group); 1219 if (addGroup != null) { 1220 mAddGroupImageView = getLayoutInflater().inflate( 1221 R.layout.add_group_menu_item, null, false); 1222 View item = mAddGroupImageView.findViewById(R.id.menu_item); 1223 item.setOnClickListener(new OnClickListener() { 1224 @Override 1225 public void onClick(View v) { 1226 createNewGroupWithAccountDisambiguation(); 1227 } 1228 }); 1229 addGroup.setActionView(mAddGroupImageView); 1230 } 1231 return true; 1232 } 1233 1234 private void invalidateOptionsMenuIfNeeded() { 1235 if (isOptionsMenuChanged()) { 1236 invalidateOptionsMenu(); 1237 } 1238 } 1239 1240 public boolean isOptionsMenuChanged() { 1241 if (mOptionsMenuContactsAvailable != areContactsAvailable()) { 1242 return true; 1243 } 1244 1245 if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) { 1246 return true; 1247 } 1248 1249 if (mContactDetailLoaderFragment != null && 1250 mContactDetailLoaderFragment.isOptionsMenuChanged()) { 1251 return true; 1252 } 1253 1254 if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) { 1255 return true; 1256 } 1257 1258 return false; 1259 } 1260 1261 @Override 1262 public boolean onPrepareOptionsMenu(Menu menu) { 1263 mOptionsMenuContactsAvailable = areContactsAvailable(); 1264 if (!mOptionsMenuContactsAvailable) { 1265 // STOPSHIP Remove makeAllMenuItemsVisible()when STOPSHIP in onCreateOptionsMenu() is 1266 // fixed. 1267 makeAllMenuItemsVisible(menu, false); 1268 return false; 1269 } 1270 makeAllMenuItemsVisible(menu, true); 1271 1272 final MenuItem searchMenu = menu.findItem(R.id.menu_search); 1273 final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact); 1274 final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter); 1275 1276 MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group); 1277 if (addGroupMenu == null) { 1278 addGroupMenu = menu.findItem(R.id.menu_custom_add_group); 1279 } 1280 1281 if (mActionBarAdapter.isSearchMode()) { 1282 addContactMenu.setVisible(false); 1283 addGroupMenu.setVisible(false); 1284 contactsFilterMenu.setVisible(false); 1285 } else { 1286 switch (mActionBarAdapter.getCurrentTab()) { 1287 case FAVORITES: 1288 addContactMenu.setVisible(false); 1289 addGroupMenu.setVisible(false); 1290 contactsFilterMenu.setVisible(false); 1291 break; 1292 case ALL: 1293 addContactMenu.setVisible(true); 1294 addGroupMenu.setVisible(false); 1295 contactsFilterMenu.setVisible(true); 1296 break; 1297 case GROUPS: 1298 // Do not display the "new group" button if no accounts are available 1299 if (areAccountsAvailable()) { 1300 addGroupMenu.setVisible(true); 1301 } else { 1302 addGroupMenu.setVisible(false); 1303 } 1304 addContactMenu.setVisible(false); 1305 contactsFilterMenu.setVisible(false); 1306 break; 1307 } 1308 } 1309 1310 if (searchMenu != null) { 1311 // Don't show the search menu in search mode. 1312 searchMenu.setVisible(!mActionBarAdapter.isSearchMode()); 1313 } 1314 1315 1316 MenuItem settings = menu.findItem(R.id.menu_settings); 1317 if (settings != null) { 1318 settings.setVisible(!ContactsPreferenceActivity.isEmpty(this)); 1319 } 1320 1321 return true; 1322 } 1323 1324 private void makeAllMenuItemsVisible(Menu menu, boolean visible) { 1325 final int itemCount = menu.size(); 1326 for (int i = 0; i < itemCount; i++) { 1327 menu.getItem(i).setVisible(visible); 1328 } 1329 } 1330 1331 @Override 1332 public boolean onOptionsItemSelected(MenuItem item) { 1333 switch (item.getItemId()) { 1334 case android.R.id.home: { 1335 // The home icon on the action bar is pressed 1336 if (mActionBarAdapter.isUpShowing()) { 1337 // "UP" icon press -- should be treated as "back". 1338 onBackPressed(); 1339 } 1340 return true; 1341 } 1342 case R.id.menu_settings: { 1343 final Intent intent = new Intent(this, ContactsPreferenceActivity.class); 1344 // as there is only one section right now, make sure it is selected 1345 // on small screens, this also hides the section selector 1346 // Due to b/5045558, this code unfortunately only works properly on phones 1347 boolean settingsAreMultiPane = getResources().getBoolean( 1348 com.android.internal.R.bool.preferences_prefer_dual_pane); 1349 if (!settingsAreMultiPane) { 1350 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, 1351 DisplayOptionsPreferenceFragment.class.getName()); 1352 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE, 1353 R.string.preference_displayOptions); 1354 } 1355 startActivity(intent); 1356 return true; 1357 } 1358 case R.id.menu_contacts_filter: { 1359 final Intent intent = new Intent(this, AccountFilterActivity.class); 1360 startActivityForResult(intent, SUBACTIVITY_ACCOUNT_FILTER); 1361 return true; 1362 } 1363 case R.id.menu_search: { 1364 onSearchRequested(); 1365 return true; 1366 } 1367 case R.id.menu_add_contact: { 1368 final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 1369 startActivity(intent); 1370 return true; 1371 } 1372 case R.id.menu_add_group: { 1373 createNewGroupWithAccountDisambiguation(); 1374 return true; 1375 } 1376 case R.id.menu_import_export: { 1377 ImportExportDialogFragment.show(getFragmentManager()); 1378 return true; 1379 } 1380 case R.id.menu_accounts: { 1381 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); 1382 intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] { 1383 ContactsContract.AUTHORITY 1384 }); 1385 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1386 startActivity(intent); 1387 return true; 1388 } 1389 } 1390 return false; 1391 } 1392 1393 private void createNewGroupWithAccountDisambiguation() { 1394 final List<AccountWithDataSet> accounts = 1395 AccountTypeManager.getInstance(this).getAccounts(true); 1396 if (accounts.size() <= 1 || mAddGroupImageView == null) { 1397 // No account to choose or no control to anchor the popup-menu to 1398 // ==> just go straight to the editor which will disambig if necessary 1399 final Intent intent = new Intent(this, GroupEditorActivity.class); 1400 intent.setAction(Intent.ACTION_INSERT); 1401 startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); 1402 return; 1403 } 1404 1405 final ListPopupWindow popup = new ListPopupWindow(this, null); 1406 popup.setWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width)); 1407 popup.setAnchorView(mAddGroupImageView); 1408 // Create a list adapter with all writeable accounts (assume that the writeable accounts all 1409 // allow group creation). 1410 final AccountsListAdapter adapter = new AccountsListAdapter(this, true); 1411 popup.setAdapter(adapter); 1412 popup.setOnItemClickListener(new OnItemClickListener() { 1413 @Override 1414 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1415 popup.dismiss(); 1416 AccountWithDataSet account = adapter.getItem(position); 1417 final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); 1418 intent.setAction(Intent.ACTION_INSERT); 1419 intent.putExtra(Intents.Insert.ACCOUNT, account); 1420 intent.putExtra(Intents.Insert.DATA_SET, account.dataSet); 1421 startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); 1422 } 1423 }); 1424 popup.setModal(true); 1425 popup.show(); 1426 } 1427 1428 @Override 1429 public boolean onSearchRequested() { // Search key pressed. 1430 mActionBarAdapter.setSearchMode(true); 1431 return true; 1432 } 1433 1434 @Override 1435 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1436 switch (requestCode) { 1437 case SUBACTIVITY_ACCOUNT_FILTER: { 1438 if (resultCode == Activity.RESULT_OK) { 1439 ContactListFilter filter = (ContactListFilter) data.getParcelableExtra( 1440 AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER); 1441 if (filter == null) { 1442 return; 1443 } 1444 if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) { 1445 mContactListFilterController.selectCustomFilter(); 1446 } else { 1447 mContactListFilterController.setContactListFilter(filter, true); 1448 } 1449 } 1450 break; 1451 } 1452 1453 case SUBACTIVITY_NEW_GROUP: 1454 case SUBACTIVITY_EDIT_GROUP: { 1455 if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) { 1456 mRequest.setActionCode(ContactsRequest.ACTION_GROUP); 1457 mGroupsFragment.setSelectedUri(data.getData()); 1458 } 1459 break; 1460 } 1461 1462 // TODO: Using the new startActivityWithResultFromFragment API this should not be needed 1463 // anymore 1464 case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER: 1465 if (resultCode == RESULT_OK) { 1466 mAllFragment.onPickerResult(data); 1467 } 1468 1469// TODO fix or remove multipicker code 1470// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) { 1471// // Finish the activity if the sub activity was canceled as back key is used 1472// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES. 1473// finish(); 1474// } 1475// break; 1476 } 1477 } 1478 1479 @Override 1480 public boolean onKeyDown(int keyCode, KeyEvent event) { 1481 // TODO move to the fragment 1482 switch (keyCode) { 1483// case KeyEvent.KEYCODE_CALL: { 1484// if (callSelection()) { 1485// return true; 1486// } 1487// break; 1488// } 1489 1490 case KeyEvent.KEYCODE_DEL: { 1491 if (deleteSelection()) { 1492 return true; 1493 } 1494 break; 1495 } 1496 default: { 1497 // Bring up the search UI if the user starts typing 1498 final int unicodeChar = event.getUnicodeChar(); 1499 if (unicodeChar != 0 && !Character.isWhitespace(unicodeChar)) { 1500 String query = new String(new int[]{ unicodeChar }, 0, 1); 1501 if (!mActionBarAdapter.isSearchMode()) { 1502 mActionBarAdapter.setQueryString(query); 1503 mActionBarAdapter.setSearchMode(true); 1504 return true; 1505 } 1506 } 1507 } 1508 } 1509 1510 return super.onKeyDown(keyCode, event); 1511 } 1512 1513 @Override 1514 public void onBackPressed() { 1515 if (mActionBarAdapter.isSearchMode()) { 1516 mActionBarAdapter.setSearchMode(false); 1517 } else { 1518 super.onBackPressed(); 1519 } 1520 } 1521 1522 private boolean deleteSelection() { 1523 // TODO move to the fragment 1524// if (mActionCode == ContactsRequest.ACTION_DEFAULT) { 1525// final int position = mListView.getSelectedItemPosition(); 1526// if (position != ListView.INVALID_POSITION) { 1527// Uri contactUri = getContactUri(position); 1528// if (contactUri != null) { 1529// doContactDelete(contactUri); 1530// return true; 1531// } 1532// } 1533// } 1534 return false; 1535 } 1536 1537 @Override 1538 protected void onSaveInstanceState(Bundle outState) { 1539 super.onSaveInstanceState(outState); 1540 mActionBarAdapter.onSaveInstanceState(outState); 1541 if (mContactDetailLayoutController != null) { 1542 mContactDetailLayoutController.onSaveInstanceState(outState); 1543 } 1544 1545 // Clear the listener to make sure we don't get callbacks after onSaveInstanceState, 1546 // in order to avoid doing fragment transactions after it. 1547 // TODO Figure out a better way to deal with the issue. 1548 mActionBarAdapter.setListener(null); 1549 } 1550 1551 @Override 1552 public DialogManager getDialogManager() { 1553 return mDialogManager; 1554 } 1555 1556 // Visible for testing 1557 public ContactBrowseListFragment getListFragment() { 1558 return mAllFragment; 1559 } 1560 1561 // Visible for testing 1562 public ContactDetailFragment getDetailFragment() { 1563 return mContactDetailFragment; 1564 } 1565} 1566