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