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