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