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