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