PeopleActivity.java revision 8a6f4ade05e5a8a89d91078ef9c22944450ac8ba
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 android.app.Fragment; 20import android.app.FragmentManager; 21import android.app.FragmentTransaction; 22import android.content.Intent; 23import android.graphics.Rect; 24import android.net.Uri; 25import android.os.Bundle; 26import android.os.Parcelable; 27import android.os.UserManager; 28import android.preference.PreferenceActivity; 29import android.provider.ContactsContract; 30import android.provider.ContactsContract.Contacts; 31import android.provider.ContactsContract.ProviderStatus; 32import android.provider.ContactsContract.QuickContact; 33import android.provider.Settings; 34import android.support.v13.app.FragmentPagerAdapter; 35import android.support.v4.view.PagerAdapter; 36import android.support.v4.view.ViewPager; 37import android.text.TextUtils; 38import android.util.Log; 39import android.view.KeyCharacterMap; 40import android.view.KeyEvent; 41import android.view.Menu; 42import android.view.MenuInflater; 43import android.view.MenuItem; 44import android.view.View; 45import android.view.ViewGroup; 46import android.widget.ImageButton; 47 48import com.android.contacts.ContactsActivity; 49import com.android.contacts.R; 50import com.android.contacts.activities.ActionBarAdapter.TabState; 51import com.android.contacts.common.ContactsUtils; 52import com.android.contacts.common.dialog.ClearFrequentsDialog; 53import com.android.contacts.interactions.ContactDeletionInteraction; 54import com.android.contacts.common.interactions.ImportExportDialogFragment; 55import com.android.contacts.common.list.ContactEntryListFragment; 56import com.android.contacts.common.list.ContactListFilter; 57import com.android.contacts.common.list.ContactListFilterController; 58import com.android.contacts.common.list.ContactTileAdapter.DisplayType; 59import com.android.contacts.list.ContactTileListFragment; 60import com.android.contacts.list.ContactsIntentResolver; 61import com.android.contacts.list.ContactsRequest; 62import com.android.contacts.list.ContactsUnavailableFragment; 63import com.android.contacts.list.DefaultContactBrowseListFragment; 64import com.android.contacts.common.list.DirectoryListLoader; 65import com.android.contacts.list.OnContactBrowserActionListener; 66import com.android.contacts.list.OnContactsUnavailableActionListener; 67import com.android.contacts.list.ProviderStatusWatcher; 68import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener; 69import com.android.contacts.preference.ContactsPreferenceActivity; 70import com.android.contacts.preference.DisplayOptionsPreferenceFragment; 71import com.android.contacts.common.util.AccountFilterUtil; 72import com.android.contacts.common.util.ViewUtil; 73import com.android.contacts.quickcontact.QuickContactActivity; 74import com.android.contacts.util.AccountPromptUtils; 75import com.android.contacts.common.util.Constants; 76import com.android.contacts.util.DialogManager; 77import com.android.contacts.util.HelpUtils; 78 79import java.util.Locale; 80import java.util.concurrent.atomic.AtomicInteger; 81 82/** 83 * Displays a list to browse contacts. 84 */ 85public class PeopleActivity extends ContactsActivity implements 86 View.OnCreateContextMenuListener, 87 View.OnClickListener, 88 ActionBarAdapter.Listener, 89 DialogManager.DialogShowingViewActivity, 90 ContactListFilterController.ContactListFilterListener, 91 ProviderStatusListener { 92 93 private static final String TAG = "PeopleActivity"; 94 95 private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!"; 96 97 // These values needs to start at 2. See {@link ContactEntryListFragment}. 98 private static final int SUBACTIVITY_ACCOUNT_FILTER = 2; 99 100 private final DialogManager mDialogManager = new DialogManager(this); 101 102 private ContactsIntentResolver mIntentResolver; 103 private ContactsRequest mRequest; 104 105 private ActionBarAdapter mActionBarAdapter; 106 107 private ContactTileListFragment.Listener mFavoritesFragmentListener = 108 new StrequentContactListFragmentListener(); 109 110 private ContactListFilterController mContactListFilterController; 111 112 private ContactsUnavailableFragment mContactsUnavailableFragment; 113 private ProviderStatusWatcher mProviderStatusWatcher; 114 private ProviderStatusWatcher.Status mProviderStatus; 115 116 private boolean mOptionsMenuContactsAvailable; 117 118 /** 119 * Showing a list of Contacts. Also used for showing search results in search mode. 120 */ 121 private DefaultContactBrowseListFragment mAllFragment; 122 private ContactTileListFragment mFavoritesFragment; 123 124 /** ViewPager for swipe */ 125 private ViewPager mTabPager; 126 private TabPagerAdapter mTabPagerAdapter; 127 private final TabPagerListener mTabPagerListener = new TabPagerListener(); 128 129 private boolean mEnableDebugMenuOptions; 130 131 /** 132 * True if this activity instance is a re-created one. i.e. set true after orientation change. 133 * This is set in {@link #onCreate} for later use in {@link #onStart}. 134 */ 135 private boolean mIsRecreatedInstance; 136 137 /** 138 * If {@link #configureFragments(boolean)} is already called. Used to avoid calling it twice 139 * in {@link #onStart}. 140 * (This initialization only needs to be done once in onStart() when the Activity was just 141 * created from scratch -- i.e. onCreate() was just called) 142 */ 143 private boolean mFragmentInitialized; 144 145 /** 146 * This is to disable {@link #onOptionsItemSelected} when we trying to stop the activity. 147 */ 148 private boolean mDisableOptionItemSelected; 149 150 /** Sequential ID assigned to each instance; used for logging */ 151 private final int mInstanceId; 152 private static final AtomicInteger sNextInstanceId = new AtomicInteger(); 153 154 public PeopleActivity() { 155 mInstanceId = sNextInstanceId.getAndIncrement(); 156 mIntentResolver = new ContactsIntentResolver(this); 157 mProviderStatusWatcher = ProviderStatusWatcher.getInstance(this); 158 } 159 160 @Override 161 public String toString() { 162 // Shown on logcat 163 return String.format("%s@%d", getClass().getSimpleName(), mInstanceId); 164 } 165 166 public boolean areContactsAvailable() { 167 return (mProviderStatus != null) 168 && mProviderStatus.status == ProviderStatus.STATUS_NORMAL; 169 } 170 171 private boolean areContactWritableAccountsAvailable() { 172 return ContactsUtils.areContactWritableAccountsAvailable(this); 173 } 174 175 private boolean areGroupWritableAccountsAvailable() { 176 return ContactsUtils.areGroupWritableAccountsAvailable(this); 177 } 178 179 /** 180 * Initialize fragments that are (or may not be) in the layout. 181 * 182 * For the fragments that are in the layout, we initialize them in 183 * {@link #createViewsAndFragments(Bundle)} after inflating the layout. 184 * 185 * However, the {@link ContactsUnavailableFragment} is a special fragment which may not 186 * be in the layout, so we have to do the initialization here. 187 * 188 * The ContactsUnavailableFragment is always created at runtime. 189 */ 190 @Override 191 public void onAttachFragment(Fragment fragment) { 192 if (fragment instanceof ContactsUnavailableFragment) { 193 mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment; 194 mContactsUnavailableFragment.setOnContactsUnavailableActionListener( 195 new ContactsUnavailableFragmentListener()); 196 } 197 } 198 199 @Override 200 protected void onCreate(Bundle savedState) { 201 if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) { 202 Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start"); 203 } 204 super.onCreate(savedState); 205 206 if (!processIntent(false)) { 207 finish(); 208 return; 209 } 210 mContactListFilterController = ContactListFilterController.getInstance(this); 211 mContactListFilterController.checkFilterValidity(false); 212 mContactListFilterController.addListener(this); 213 214 mProviderStatusWatcher.addListener(this); 215 216 mIsRecreatedInstance = (savedState != null); 217 createViewsAndFragments(savedState); 218 219 if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) { 220 Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish"); 221 } 222 } 223 224 @Override 225 protected void onNewIntent(Intent intent) { 226 setIntent(intent); 227 if (!processIntent(true)) { 228 finish(); 229 return; 230 } 231 mActionBarAdapter.initialize(null, mRequest); 232 233 mContactListFilterController.checkFilterValidity(false); 234 235 // Re-configure fragments. 236 configureFragments(true /* from request */); 237 invalidateOptionsMenuIfNeeded(); 238 } 239 240 /** 241 * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect 242 * is needed. 243 * 244 * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}. 245 * @return {@code true} if {@link PeopleActivity} should continue running. {@code false} 246 * if it shouldn't, in which case the caller should finish() itself and shouldn't do 247 * farther initialization. 248 */ 249 private boolean processIntent(boolean forNewIntent) { 250 // Extract relevant information from the intent 251 mRequest = mIntentResolver.resolveIntent(getIntent()); 252 if (Log.isLoggable(TAG, Log.DEBUG)) { 253 Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent 254 + " intent=" + getIntent() + " request=" + mRequest); 255 } 256 if (!mRequest.isValid()) { 257 setResult(RESULT_CANCELED); 258 return false; 259 } 260 261 Intent redirect = mRequest.getRedirectIntent(); 262 if (redirect != null) { 263 // Need to start a different activity 264 startActivity(redirect); 265 return false; 266 } 267 268 if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT) { 269 redirect = new Intent(this, ContactDetailActivity.class); 270 redirect.setAction(Intent.ACTION_VIEW); 271 redirect.setData(mRequest.getContactUri()); 272 startActivity(redirect); 273 return false; 274 } 275 return true; 276 } 277 278 private void createViewsAndFragments(Bundle savedState) { 279 setContentView(R.layout.people_activity); 280 281 final FragmentManager fragmentManager = getFragmentManager(); 282 283 // Hide all tabs (the current tab will later be reshown once a tab is selected) 284 final FragmentTransaction transaction = fragmentManager.beginTransaction(); 285 286 mTabPager = getView(R.id.tab_pager); 287 mTabPagerAdapter = new TabPagerAdapter(); 288 mTabPager.setAdapter(mTabPagerAdapter); 289 mTabPager.setOnPageChangeListener(mTabPagerListener); 290 291 final String FAVORITE_TAG = "tab-pager-favorite"; 292 final String ALL_TAG = "tab-pager-all"; 293 294 // Create the fragments and add as children of the view pager. 295 // The pager adapter will only change the visibility; it'll never create/destroy 296 // fragments. 297 // However, if it's after screen rotation, the fragments have been re-created by 298 // the fragment manager, so first see if there're already the target fragments 299 // existing. 300 mFavoritesFragment = (ContactTileListFragment) 301 fragmentManager.findFragmentByTag(FAVORITE_TAG); 302 mAllFragment = (DefaultContactBrowseListFragment) 303 fragmentManager.findFragmentByTag(ALL_TAG); 304 305 if (mFavoritesFragment == null) { 306 mFavoritesFragment = new ContactTileListFragment(); 307 mAllFragment = new DefaultContactBrowseListFragment(); 308 309 transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG); 310 transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG); 311 } 312 313 mFavoritesFragment.setListener(mFavoritesFragmentListener); 314 315 mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener()); 316 317 // Hide all fragments for now. We adjust visibility when we get onSelectedTabChanged() 318 // from ActionBarAdapter. 319 transaction.hide(mFavoritesFragment); 320 transaction.hide(mAllFragment); 321 322 transaction.commitAllowingStateLoss(); 323 fragmentManager.executePendingTransactions(); 324 325 // Setting Properties after fragment is created 326 mFavoritesFragment.setDisplayType(DisplayType.STREQUENT); 327 328 // Configure action bar 329 mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar()); 330 mActionBarAdapter.initialize(savedState, mRequest); 331 332 // Configure action button 333 final View floatingActionButtonContainer = findViewById( 334 R.id.floating_action_button_container); 335 ViewUtil.setupFloatingActionButton(floatingActionButtonContainer, getResources()); 336 final ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button); 337 floatingActionButton.setOnClickListener(this); 338 339 invalidateOptionsMenuIfNeeded(); 340 } 341 342 @Override 343 protected void onStart() { 344 if (!mFragmentInitialized) { 345 mFragmentInitialized = true; 346 /* Configure fragments if we haven't. 347 * 348 * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}. 349 * 350 * However, because this method may indirectly touch views in fragments but fragments 351 * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT 352 * have views until {@link Activity#onCreate} finishes (they would if they were inflated 353 * from a layout), we need to do it here in {@link #onStart()}. 354 * 355 * (When {@link Fragment#onCreateView} is called is different in the former case and 356 * in the latter case, unfortunately.) 357 * 358 * Also, we skip most of the work in it if the activity is a re-created one. 359 * (so the argument.) 360 */ 361 configureFragments(!mIsRecreatedInstance); 362 } 363 super.onStart(); 364 } 365 366 @Override 367 protected void onPause() { 368 mOptionsMenuContactsAvailable = false; 369 mProviderStatusWatcher.stop(); 370 super.onPause(); 371 } 372 373 @Override 374 protected void onResume() { 375 super.onResume(); 376 377 mProviderStatusWatcher.start(); 378 updateViewConfiguration(true); 379 380 // Re-register the listener, which may have been cleared when onSaveInstanceState was 381 // called. See also: onSaveInstanceState 382 mActionBarAdapter.setListener(this); 383 mDisableOptionItemSelected = false; 384 if (mTabPager != null) { 385 mTabPager.setOnPageChangeListener(mTabPagerListener); 386 } 387 // Current tab may have changed since the last onSaveInstanceState(). Make sure 388 // the actual contents match the tab. 389 updateFragmentsVisibility(); 390 } 391 392 @Override 393 protected void onStop() { 394 super.onStop(); 395 } 396 397 @Override 398 protected void onDestroy() { 399 mProviderStatusWatcher.removeListener(this); 400 401 // Some of variables will be null if this Activity redirects Intent. 402 // See also onCreate() or other methods called during the Activity's initialization. 403 if (mActionBarAdapter != null) { 404 mActionBarAdapter.setListener(null); 405 } 406 if (mContactListFilterController != null) { 407 mContactListFilterController.removeListener(this); 408 } 409 410 super.onDestroy(); 411 } 412 413 private void configureFragments(boolean fromRequest) { 414 if (fromRequest) { 415 ContactListFilter filter = null; 416 int actionCode = mRequest.getActionCode(); 417 boolean searchMode = mRequest.isSearchMode(); 418 final int tabToOpen; 419 switch (actionCode) { 420 case ContactsRequest.ACTION_ALL_CONTACTS: 421 filter = ContactListFilter.createFilterWithType( 422 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 423 tabToOpen = TabState.ALL; 424 break; 425 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES: 426 filter = ContactListFilter.createFilterWithType( 427 ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY); 428 tabToOpen = TabState.ALL; 429 break; 430 431 case ContactsRequest.ACTION_FREQUENT: 432 case ContactsRequest.ACTION_STREQUENT: 433 case ContactsRequest.ACTION_STARRED: 434 tabToOpen = TabState.FAVORITES; 435 break; 436 case ContactsRequest.ACTION_VIEW_CONTACT: 437 tabToOpen = TabState.ALL; 438 break; 439 default: 440 tabToOpen = -1; 441 break; 442 } 443 if (tabToOpen != -1) { 444 mActionBarAdapter.setCurrentTab(tabToOpen); 445 } 446 447 if (filter != null) { 448 mContactListFilterController.setContactListFilter(filter, false); 449 searchMode = false; 450 } 451 452 if (mRequest.getContactUri() != null) { 453 searchMode = false; 454 } 455 456 mActionBarAdapter.setSearchMode(searchMode); 457 configureContactListFragmentForRequest(); 458 } 459 460 configureContactListFragment(); 461 462 invalidateOptionsMenuIfNeeded(); 463 } 464 465 @Override 466 public void onContactListFilterChanged() { 467 if (mAllFragment == null || !mAllFragment.isAdded()) { 468 return; 469 } 470 471 mAllFragment.setFilter(mContactListFilterController.getFilter()); 472 473 invalidateOptionsMenuIfNeeded(); 474 } 475 476 /** 477 * Handler for action bar actions. 478 */ 479 @Override 480 public void onAction(int action) { 481 switch (action) { 482 case ActionBarAdapter.Listener.Action.START_SEARCH_MODE: 483 // Tell the fragments that we're in the search mode 484 configureFragments(false /* from request */); 485 updateFragmentsVisibility(); 486 invalidateOptionsMenu(); 487 break; 488 case ActionBarAdapter.Listener.Action.STOP_SEARCH_MODE: 489 setQueryTextToFragment(""); 490 updateFragmentsVisibility(); 491 invalidateOptionsMenu(); 492 break; 493 case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY: 494 final String queryString = mActionBarAdapter.getQueryString(); 495 setQueryTextToFragment(queryString); 496 updateDebugOptionsVisibility( 497 ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString)); 498 break; 499 default: 500 throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action); 501 } 502 } 503 504 @Override 505 public void onSelectedTabChanged() { 506 updateFragmentsVisibility(); 507 } 508 509 @Override 510 public void onUpButtonPressed() { 511 onBackPressed(); 512 } 513 514 private void updateDebugOptionsVisibility(boolean visible) { 515 if (mEnableDebugMenuOptions != visible) { 516 mEnableDebugMenuOptions = visible; 517 invalidateOptionsMenu(); 518 } 519 } 520 521 /** 522 * Updates the fragment/view visibility according to the current mode, such as 523 * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}. 524 */ 525 private void updateFragmentsVisibility() { 526 int tab = mActionBarAdapter.getCurrentTab(); 527 528 if (mActionBarAdapter.isSearchMode()) { 529 mTabPagerAdapter.setSearchMode(true); 530 } else { 531 // No smooth scrolling if quitting from the search mode. 532 final boolean wasSearchMode = mTabPagerAdapter.isSearchMode(); 533 mTabPagerAdapter.setSearchMode(false); 534 if (mTabPager.getCurrentItem() != tab) { 535 mTabPager.setCurrentItem(tab, !wasSearchMode); 536 } 537 } 538 invalidateOptionsMenu(); 539 showEmptyStateForTab(tab); 540 } 541 542 private void showEmptyStateForTab(int tab) { 543 if (mContactsUnavailableFragment != null) { 544 switch (tab) { 545 case TabState.FAVORITES: 546 mContactsUnavailableFragment.setMessageText( 547 R.string.listTotalAllContactsZeroStarred, -1); 548 break; 549 case TabState.ALL: 550 mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1); 551 break; 552 } 553 } 554 } 555 556 private class TabPagerListener implements ViewPager.OnPageChangeListener { 557 558 // This package-protected constructor is here because of a possible compiler bug. 559 // PeopleActivity$1.class should be generated due to the private outer/inner class access 560 // needed here. But for some reason, PeopleActivity$1.class is missing. 561 // Since $1 class is needed as a jvm work around to get access to the inner class, 562 // changing the constructor to package-protected or public will solve the problem. 563 // To verify whether $1 class is needed, javap PeopleActivity$TabPagerListener and look for 564 // references to PeopleActivity$1. 565 // 566 // When the constructor is private and PeopleActivity$1.class is missing, proguard will 567 // correctly catch this and throw warnings and error out the build on user/userdebug builds. 568 // 569 // All private inner classes below also need this fix. 570 TabPagerListener() {} 571 572 @Override 573 public void onPageScrollStateChanged(int state) { 574 } 575 576 @Override 577 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 578 } 579 580 @Override 581 public void onPageSelected(int position) { 582 // Make sure not in the search mode, in which case position != TabState.ordinal(). 583 if (!mTabPagerAdapter.isSearchMode()) { 584 mActionBarAdapter.setCurrentTab(position, false); 585 showEmptyStateForTab(position); 586 invalidateOptionsMenu(); 587 } 588 } 589 } 590 591 /** 592 * Adapter for the {@link ViewPager}. Unlike {@link FragmentPagerAdapter}, 593 * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/ 594 * {@link #destroyItem} show/hide fragments instead of attaching/detaching. 595 * 596 * In search mode, we always show the "all" fragment, and disable the swipe. We change the 597 * number of items to 1 to disable the swipe. 598 * 599 * TODO figure out a more straight way to disable swipe. 600 */ 601 private class TabPagerAdapter extends PagerAdapter { 602 private final FragmentManager mFragmentManager; 603 private FragmentTransaction mCurTransaction = null; 604 605 private boolean mTabPagerAdapterSearchMode; 606 607 private Fragment mCurrentPrimaryItem; 608 609 public TabPagerAdapter() { 610 mFragmentManager = getFragmentManager(); 611 } 612 613 public boolean isSearchMode() { 614 return mTabPagerAdapterSearchMode; 615 } 616 617 public void setSearchMode(boolean searchMode) { 618 if (searchMode == mTabPagerAdapterSearchMode) { 619 return; 620 } 621 mTabPagerAdapterSearchMode = searchMode; 622 notifyDataSetChanged(); 623 } 624 625 @Override 626 public int getCount() { 627 return mTabPagerAdapterSearchMode ? 1 : TabState.COUNT; 628 } 629 630 /** Gets called when the number of items changes. */ 631 @Override 632 public int getItemPosition(Object object) { 633 if (mTabPagerAdapterSearchMode) { 634 if (object == mAllFragment) { 635 return 0; // Only 1 page in search mode 636 } 637 } else { 638 if (object == mFavoritesFragment) { 639 return TabState.FAVORITES; 640 } 641 if (object == mAllFragment) { 642 return TabState.ALL; 643 } 644 } 645 return POSITION_NONE; 646 } 647 648 @Override 649 public void startUpdate(ViewGroup container) { 650 } 651 652 private Fragment getFragment(int position) { 653 if (mTabPagerAdapterSearchMode) { 654 if (position != 0) { 655 // This has only been observed in monkey tests. 656 // Let's log this issue, but not crash 657 Log.w(TAG, "Request fragment at position=" + position + ", eventhough we " + 658 "are in search mode"); 659 } 660 return mAllFragment; 661 } else { 662 if (position == TabState.FAVORITES) { 663 return mFavoritesFragment; 664 } else if (position == TabState.ALL) { 665 return mAllFragment; 666 } 667 } 668 throw new IllegalArgumentException("position: " + position); 669 } 670 671 @Override 672 public Object instantiateItem(ViewGroup container, int position) { 673 if (mCurTransaction == null) { 674 mCurTransaction = mFragmentManager.beginTransaction(); 675 } 676 Fragment f = getFragment(position); 677 mCurTransaction.show(f); 678 679 // Non primary pages are not visible. 680 f.setUserVisibleHint(f == mCurrentPrimaryItem); 681 return f; 682 } 683 684 @Override 685 public void destroyItem(ViewGroup container, int position, Object object) { 686 if (mCurTransaction == null) { 687 mCurTransaction = mFragmentManager.beginTransaction(); 688 } 689 mCurTransaction.hide((Fragment) object); 690 } 691 692 @Override 693 public void finishUpdate(ViewGroup container) { 694 if (mCurTransaction != null) { 695 mCurTransaction.commitAllowingStateLoss(); 696 mCurTransaction = null; 697 mFragmentManager.executePendingTransactions(); 698 } 699 } 700 701 @Override 702 public boolean isViewFromObject(View view, Object object) { 703 return ((Fragment) object).getView() == view; 704 } 705 706 @Override 707 public void setPrimaryItem(ViewGroup container, int position, Object object) { 708 Fragment fragment = (Fragment) object; 709 if (mCurrentPrimaryItem != fragment) { 710 if (mCurrentPrimaryItem != null) { 711 mCurrentPrimaryItem.setUserVisibleHint(false); 712 } 713 if (fragment != null) { 714 fragment.setUserVisibleHint(true); 715 } 716 mCurrentPrimaryItem = fragment; 717 } 718 } 719 720 @Override 721 public Parcelable saveState() { 722 return null; 723 } 724 725 @Override 726 public void restoreState(Parcelable state, ClassLoader loader) { 727 } 728 } 729 730 private void setQueryTextToFragment(String query) { 731 mAllFragment.setQueryString(query, true); 732 mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode()); 733 } 734 735 private void configureContactListFragmentForRequest() { 736 Uri contactUri = mRequest.getContactUri(); 737 if (contactUri != null) { 738 mAllFragment.setSelectedContactUri(contactUri); 739 } 740 741 mAllFragment.setFilter(mContactListFilterController.getFilter()); 742 setQueryTextToFragment(mActionBarAdapter.getQueryString()); 743 744 if (mRequest.isDirectorySearchEnabled()) { 745 mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT); 746 } else { 747 mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); 748 } 749 } 750 751 private void configureContactListFragment() { 752 // Filter may be changed when this Activity is in background. 753 mAllFragment.setFilter(mContactListFilterController.getFilter()); 754 755 mAllFragment.setVerticalScrollbarPosition(getScrollBarPosition()); 756 mAllFragment.setSelectionVisible(false); 757 } 758 759 private int getScrollBarPosition() { 760 return isRTL() ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT; 761 } 762 763 private boolean isRTL() { 764 final Locale locale = Locale.getDefault(); 765 return TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL; 766 } 767 768 @Override 769 public void onProviderStatusChange() { 770 updateViewConfiguration(false); 771 } 772 773 private void updateViewConfiguration(boolean forceUpdate) { 774 ProviderStatusWatcher.Status providerStatus = mProviderStatusWatcher.getProviderStatus(); 775 if (!forceUpdate && (mProviderStatus != null) 776 && (providerStatus.status == mProviderStatus.status)) return; 777 mProviderStatus = providerStatus; 778 779 View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view); 780 781 if (mProviderStatus.status == ProviderStatus.STATUS_NORMAL) { 782 // Ensure that the mTabPager is visible; we may have made it invisible below. 783 contactsUnavailableView.setVisibility(View.GONE); 784 if (mTabPager != null) { 785 mTabPager.setVisibility(View.VISIBLE); 786 } 787 788 if (mAllFragment != null) { 789 mAllFragment.setEnabled(true); 790 } 791 } else { 792 // If there are no accounts on the device and we should show the "no account" prompt 793 // (based on {@link SharedPreferences}), then launch the account setup activity so the 794 // user can sign-in or create an account. 795 // 796 // Also check for ability to modify accounts. In limited user mode, you can't modify 797 // accounts so there is no point sending users to account setup activity. 798 final UserManager userManager = UserManager.get(this); 799 final boolean disallowModifyAccounts = userManager.getUserRestrictions().getBoolean( 800 UserManager.DISALLOW_MODIFY_ACCOUNTS); 801 if (!disallowModifyAccounts && !areContactWritableAccountsAvailable() && 802 AccountPromptUtils.shouldShowAccountPrompt(this)) { 803 AccountPromptUtils.launchAccountPrompt(this); 804 return; 805 } 806 807 // Otherwise, continue setting up the page so that the user can still use the app 808 // without an account. 809 if (mAllFragment != null) { 810 mAllFragment.setEnabled(false); 811 } 812 if (mContactsUnavailableFragment == null) { 813 mContactsUnavailableFragment = new ContactsUnavailableFragment(); 814 mContactsUnavailableFragment.setOnContactsUnavailableActionListener( 815 new ContactsUnavailableFragmentListener()); 816 getFragmentManager().beginTransaction() 817 .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment) 818 .commitAllowingStateLoss(); 819 } 820 mContactsUnavailableFragment.updateStatus(mProviderStatus); 821 822 // Show the contactsUnavailableView, and hide the mTabPager so that we don't 823 // see it sliding in underneath the contactsUnavailableView at the edges. 824 contactsUnavailableView.setVisibility(View.VISIBLE); 825 if (mTabPager != null) { 826 mTabPager.setVisibility(View.GONE); 827 } 828 829 showEmptyStateForTab(mActionBarAdapter.getCurrentTab()); 830 } 831 832 invalidateOptionsMenuIfNeeded(); 833 } 834 835 private final class ContactBrowserActionListener implements OnContactBrowserActionListener { 836 ContactBrowserActionListener() {} 837 838 @Override 839 public void onSelectionChange() { 840 841 } 842 843 @Override 844 public void onViewContactAction(Uri contactLookupUri) { 845 Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this, 846 getCurrentFocus().getRootView(), contactLookupUri, 847 QuickContactActivity.MODE_FULLY_EXPANDED, null); 848 startActivity(intent); 849 } 850 851 @Override 852 public void onDeleteContactAction(Uri contactUri) { 853 ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); 854 } 855 856 @Override 857 public void onFinishAction() { 858 onBackPressed(); 859 } 860 861 @Override 862 public void onInvalidSelection() { 863 ContactListFilter filter; 864 ContactListFilter currentFilter = mAllFragment.getFilter(); 865 if (currentFilter != null 866 && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 867 filter = ContactListFilter.createFilterWithType( 868 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 869 mAllFragment.setFilter(filter); 870 } else { 871 filter = ContactListFilter.createFilterWithType( 872 ContactListFilter.FILTER_TYPE_SINGLE_CONTACT); 873 mAllFragment.setFilter(filter, false); 874 } 875 mContactListFilterController.setContactListFilter(filter, true); 876 } 877 } 878 879 private class ContactsUnavailableFragmentListener 880 implements OnContactsUnavailableActionListener { 881 ContactsUnavailableFragmentListener() {} 882 883 @Override 884 public void onCreateNewContactAction() { 885 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 886 } 887 888 @Override 889 public void onAddAccountAction() { 890 Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); 891 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 892 intent.putExtra(Settings.EXTRA_AUTHORITIES, 893 new String[] { ContactsContract.AUTHORITY }); 894 startActivity(intent); 895 } 896 897 @Override 898 public void onImportContactsFromFileAction() { 899 ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(), 900 PeopleActivity.class); 901 } 902 903 @Override 904 public void onFreeInternalStorageAction() { 905 startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)); 906 } 907 } 908 909 private final class StrequentContactListFragmentListener 910 implements ContactTileListFragment.Listener { 911 StrequentContactListFragmentListener() {} 912 913 @Override 914 public void onContactSelected(Uri contactUri, Rect targetRect) { 915 Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this, 916 targetRect, contactUri, QuickContactActivity.MODE_FULLY_EXPANDED, null); 917 startActivity(intent); 918 } 919 920 @Override 921 public void onCallNumberDirectly(String phoneNumber) { 922 // No need to call phone number directly from People app. 923 Log.w(TAG, "unexpected invocation of onCallNumberDirectly()"); 924 } 925 } 926 927 @Override 928 public boolean onCreateOptionsMenu(Menu menu) { 929 if (!areContactsAvailable()) { 930 // If contacts aren't available, hide all menu items. 931 return false; 932 } 933 super.onCreateOptionsMenu(menu); 934 935 MenuInflater inflater = getMenuInflater(); 936 inflater.inflate(R.menu.people_options, menu); 937 938 return true; 939 } 940 941 private void invalidateOptionsMenuIfNeeded() { 942 if (isOptionsMenuChanged()) { 943 invalidateOptionsMenu(); 944 } 945 } 946 947 public boolean isOptionsMenuChanged() { 948 if (mOptionsMenuContactsAvailable != areContactsAvailable()) { 949 return true; 950 } 951 952 if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) { 953 return true; 954 } 955 956 return false; 957 } 958 959 @Override 960 public boolean onPrepareOptionsMenu(Menu menu) { 961 mOptionsMenuContactsAvailable = areContactsAvailable(); 962 if (!mOptionsMenuContactsAvailable) { 963 return false; 964 } 965 966 // Get references to individual menu items in the menu 967 final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact); 968 final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter); 969 970 final MenuItem clearFrequentsMenu = menu.findItem(R.id.menu_clear_frequents); 971 final MenuItem helpMenu = menu.findItem(R.id.menu_help); 972 973 final boolean isSearchMode = mActionBarAdapter.isSearchMode(); 974 if (isSearchMode) { 975 addContactMenu.setVisible(false); 976 contactsFilterMenu.setVisible(false); 977 clearFrequentsMenu.setVisible(false); 978 helpMenu.setVisible(false); 979 } else { 980 switch (mActionBarAdapter.getCurrentTab()) { 981 case TabState.FAVORITES: 982 addContactMenu.setVisible(true); 983 contactsFilterMenu.setVisible(false); 984 clearFrequentsMenu.setVisible(hasFrequents()); 985 break; 986 case TabState.ALL: 987 addContactMenu.setVisible(true); 988 contactsFilterMenu.setVisible(true); 989 clearFrequentsMenu.setVisible(false); 990 break; 991 } 992 HelpUtils.prepareHelpMenuItem(this, helpMenu, R.string.help_url_people_main); 993 } 994 final boolean showMiscOptions = !isSearchMode; 995 makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions); 996 makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions); 997 makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions); 998 makeMenuItemVisible(menu, R.id.menu_settings, 999 showMiscOptions && !ContactsPreferenceActivity.isEmpty(this)); 1000 1001 // Debug options need to be visible even in search mode. 1002 makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions); 1003 1004 return true; 1005 } 1006 1007 /** 1008 * Returns whether there are any frequently contacted people being displayed 1009 * @return 1010 */ 1011 private boolean hasFrequents() { 1012 return mFavoritesFragment.hasFrequents(); 1013 } 1014 1015 private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) { 1016 MenuItem item =menu.findItem(itemId); 1017 if (item != null) { 1018 item.setVisible(visible); 1019 } 1020 } 1021 1022 @Override 1023 public boolean onOptionsItemSelected(MenuItem item) { 1024 if (mDisableOptionItemSelected) { 1025 return false; 1026 } 1027 1028 switch (item.getItemId()) { 1029 case android.R.id.home: { 1030 // The home icon on the action bar is pressed 1031 if (mActionBarAdapter.isUpShowing()) { 1032 // "UP" icon press -- should be treated as "back". 1033 onBackPressed(); 1034 } 1035 return true; 1036 } 1037 case R.id.menu_settings: { 1038 final Intent intent = new Intent(this, ContactsPreferenceActivity.class); 1039 // as there is only one section right now, make sure it is selected 1040 // on small screens, this also hides the section selector 1041 // Due to b/5045558, this code unfortunately only works properly on phones 1042 boolean settingsAreMultiPane = getResources().getBoolean( 1043 com.android.internal.R.bool.preferences_prefer_dual_pane); 1044 if (!settingsAreMultiPane) { 1045 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, 1046 DisplayOptionsPreferenceFragment.class.getName()); 1047 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE, 1048 R.string.activity_title_settings); 1049 } 1050 startActivity(intent); 1051 return true; 1052 } 1053 case R.id.menu_contacts_filter: { 1054 AccountFilterUtil.startAccountFilterActivityForResult( 1055 this, SUBACTIVITY_ACCOUNT_FILTER, 1056 mContactListFilterController.getFilter()); 1057 return true; 1058 } 1059 case R.id.menu_search: { 1060 onSearchRequested(); 1061 return true; 1062 } 1063 case R.id.menu_add_contact: { 1064 final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 1065 startActivity(intent); 1066 return true; 1067 } 1068 case R.id.menu_import_export: { 1069 ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(), 1070 PeopleActivity.class); 1071 return true; 1072 } 1073 case R.id.menu_clear_frequents: { 1074 ClearFrequentsDialog.show(getFragmentManager()); 1075 return true; 1076 } 1077 case R.id.menu_accounts: { 1078 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); 1079 intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] { 1080 ContactsContract.AUTHORITY 1081 }); 1082 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1083 startActivity(intent); 1084 return true; 1085 } 1086 case R.id.export_database: { 1087 final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE"); 1088 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1089 startActivity(intent); 1090 return true; 1091 } 1092 } 1093 return false; 1094 } 1095 1096 @Override 1097 public boolean onSearchRequested() { // Search key pressed. 1098 mActionBarAdapter.setSearchMode(true); 1099 return true; 1100 } 1101 1102 @Override 1103 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1104 switch (requestCode) { 1105 case SUBACTIVITY_ACCOUNT_FILTER: { 1106 AccountFilterUtil.handleAccountFilterResult( 1107 mContactListFilterController, resultCode, data); 1108 break; 1109 } 1110 1111 // TODO: Using the new startActivityWithResultFromFragment API this should not be needed 1112 // anymore 1113 case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER: 1114 if (resultCode == RESULT_OK) { 1115 mAllFragment.onPickerResult(data); 1116 } 1117 1118// TODO fix or remove multipicker code 1119// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) { 1120// // Finish the activity if the sub activity was canceled as back key is used 1121// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES. 1122// finish(); 1123// } 1124// break; 1125 } 1126 } 1127 1128 @Override 1129 public boolean onKeyDown(int keyCode, KeyEvent event) { 1130 // TODO move to the fragment 1131 switch (keyCode) { 1132// case KeyEvent.KEYCODE_CALL: { 1133// if (callSelection()) { 1134// return true; 1135// } 1136// break; 1137// } 1138 1139 case KeyEvent.KEYCODE_DEL: { 1140 if (deleteSelection()) { 1141 return true; 1142 } 1143 break; 1144 } 1145 default: { 1146 // Bring up the search UI if the user starts typing 1147 final int unicodeChar = event.getUnicodeChar(); 1148 if ((unicodeChar != 0) 1149 // If COMBINING_ACCENT is set, it's not a unicode character. 1150 && ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0) 1151 && !Character.isWhitespace(unicodeChar)) { 1152 String query = new String(new int[]{ unicodeChar }, 0, 1); 1153 if (!mActionBarAdapter.isSearchMode()) { 1154 mActionBarAdapter.setQueryString(query); 1155 mActionBarAdapter.setSearchMode(true); 1156 return true; 1157 } 1158 } 1159 } 1160 } 1161 1162 return super.onKeyDown(keyCode, event); 1163 } 1164 1165 @Override 1166 public void onBackPressed() { 1167 if (mActionBarAdapter.isSearchMode()) { 1168 mActionBarAdapter.setSearchMode(false); 1169 } else { 1170 super.onBackPressed(); 1171 } 1172 } 1173 1174 private boolean deleteSelection() { 1175 // TODO move to the fragment 1176// if (mActionCode == ContactsRequest.ACTION_DEFAULT) { 1177// final int position = mListView.getSelectedItemPosition(); 1178// if (position != ListView.INVALID_POSITION) { 1179// Uri contactUri = getContactUri(position); 1180// if (contactUri != null) { 1181// doContactDelete(contactUri); 1182// return true; 1183// } 1184// } 1185// } 1186 return false; 1187 } 1188 1189 @Override 1190 protected void onSaveInstanceState(Bundle outState) { 1191 super.onSaveInstanceState(outState); 1192 mActionBarAdapter.onSaveInstanceState(outState); 1193 1194 // Clear the listener to make sure we don't get callbacks after onSaveInstanceState, 1195 // in order to avoid doing fragment transactions after it. 1196 // TODO Figure out a better way to deal with the issue. 1197 mDisableOptionItemSelected = true; 1198 mActionBarAdapter.setListener(null); 1199 if (mTabPager != null) { 1200 mTabPager.setOnPageChangeListener(null); 1201 } 1202 } 1203 1204 @Override 1205 protected void onRestoreInstanceState(Bundle savedInstanceState) { 1206 super.onRestoreInstanceState(savedInstanceState); 1207 // In our own lifecycle, the focus is saved and restore but later taken away by the 1208 // ViewPager. As a hack, we force focus on the SearchView if we know that we are searching. 1209 // This fixes the keyboard going away on screen rotation 1210 if (mActionBarAdapter.isSearchMode()) { 1211 mActionBarAdapter.setFocusOnSearchView(); 1212 } 1213 } 1214 1215 @Override 1216 public DialogManager getDialogManager() { 1217 return mDialogManager; 1218 } 1219 1220 @Override 1221 public void onClick(View view) { 1222 switch (view.getId()) { 1223 case R.id.floating_action_button: 1224 Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 1225 Bundle extras = getIntent().getExtras(); 1226 if (extras != null) { 1227 intent.putExtras(extras); 1228 } 1229 startActivity(intent); 1230 break; 1231 default: 1232 Log.wtf(TAG, "Unexpected onClick event from " + view); 1233 } 1234 } 1235} 1236