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