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