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