PeopleActivity.java revision 50445e9d1efb1fa046d9a1232c973b4c96e1f52d
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.R; 23import com.android.contacts.detail.ContactDetailFragment; 24import com.android.contacts.detail.ContactDetailLayoutController; 25import com.android.contacts.detail.ContactDetailTabCarousel; 26import com.android.contacts.detail.ContactDetailUpdatesFragment; 27import com.android.contacts.detail.ContactLoaderFragment; 28import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener; 29import com.android.contacts.group.GroupBrowseListFragment; 30import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener; 31import com.android.contacts.group.GroupDetailFragment; 32import com.android.contacts.interactions.ContactDeletionInteraction; 33import com.android.contacts.interactions.ImportExportDialogFragment; 34import com.android.contacts.interactions.PhoneNumberInteraction; 35import com.android.contacts.list.AccountFilterActivity; 36import com.android.contacts.list.ContactBrowseListContextMenuAdapter; 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.ContactsIntentResolver; 43import com.android.contacts.list.ContactsRequest; 44import com.android.contacts.list.ContactsUnavailableFragment; 45import com.android.contacts.list.CustomContactListFilterActivity; 46import com.android.contacts.list.DefaultContactBrowseListFragment; 47import com.android.contacts.list.DirectoryListLoader; 48import com.android.contacts.list.OnContactBrowserActionListener; 49import com.android.contacts.list.OnContactsUnavailableActionListener; 50import com.android.contacts.list.ProviderStatusLoader; 51import com.android.contacts.list.ProviderStatusLoader.ProviderStatusListener; 52import com.android.contacts.list.StrequentContactListFragment; 53import com.android.contacts.model.AccountTypeManager; 54import com.android.contacts.preference.ContactsPreferenceActivity; 55import com.android.contacts.util.AccountSelectionUtil; 56import com.android.contacts.util.AccountsListAdapter; 57import com.android.contacts.util.DialogManager; 58import com.android.contacts.util.PhoneCapabilityTester; 59import com.android.contacts.widget.ContextMenuAdapter; 60 61import android.accounts.Account; 62import android.app.ActionBar; 63import android.app.ActionBar.Tab; 64import android.app.ActionBar.TabListener; 65import android.app.Activity; 66import android.app.Fragment; 67import android.app.FragmentManager; 68import android.app.FragmentTransaction; 69import android.content.ActivityNotFoundException; 70import android.content.ContentValues; 71import android.content.Intent; 72import android.content.res.TypedArray; 73import android.net.Uri; 74import android.os.Bundle; 75import android.os.Handler; 76import android.provider.ContactsContract; 77import android.provider.ContactsContract.Contacts; 78import android.provider.ContactsContract.Intents; 79import android.provider.ContactsContract.ProviderStatus; 80import android.provider.Settings; 81import android.support.v4.view.ViewPager; 82import android.util.Log; 83import android.view.KeyEvent; 84import android.view.Menu; 85import android.view.MenuInflater; 86import android.view.MenuItem; 87import android.view.View; 88import android.view.View.OnClickListener; 89import android.view.Window; 90import android.widget.AdapterView; 91import android.widget.AdapterView.OnItemClickListener; 92import android.widget.ListPopupWindow; 93import android.widget.Toast; 94 95import java.util.ArrayList; 96 97/** 98 * Displays a list to browse contacts. For xlarge screens, this also displays a detail-pane on 99 * the right. 100 */ 101public class PeopleActivity extends ContactsActivity 102 implements View.OnCreateContextMenuListener, ActionBarAdapter.Listener, 103 DialogManager.DialogShowingViewActivity, 104 ContactListFilterController.ContactListFilterListener, ProviderStatusListener { 105 106 private static final String TAG = "PeopleActivity"; 107 108 private static final int SUBACTIVITY_NEW_CONTACT = 2; 109 private static final int SUBACTIVITY_EDIT_CONTACT = 3; 110 private static final int SUBACTIVITY_NEW_GROUP = 4; 111 private static final int SUBACTIVITY_EDIT_GROUP = 5; 112 private static final int SUBACTIVITY_ACCOUNT_FILTER = 6; 113 private static final int SUBACTIVITY_CUSTOMIZE_FILTER = 7; 114 115 private static final String KEY_SEARCH_MODE = "searchMode"; 116 117 private DialogManager mDialogManager = new DialogManager(this); 118 119 private ContactsIntentResolver mIntentResolver; 120 private ContactsRequest mRequest; 121 122 private ActionBarAdapter mActionBarAdapter; 123 124 private boolean mSearchMode; 125 126 private ContactDetailFragment mContactDetailFragment; 127 private ContactDetailUpdatesFragment mContactDetailUpdatesFragment; 128 private final ContactDetailFragmentListener mContactDetailFragmentListener = 129 new ContactDetailFragmentListener(); 130 131 private ContactLoaderFragment mContactDetailLoaderFragment; 132 private final ContactDetailLoaderFragmentListener mContactDetailLoaderFragmentListener = 133 new ContactDetailLoaderFragmentListener(); 134 135 private GroupDetailFragment mGroupDetailFragment; 136 private final GroupDetailFragmentListener mGroupDetailFragmentListener = 137 new GroupDetailFragmentListener(); 138 139 private StrequentContactListFragment.Listener mFavoritesFragmentListener = 140 new StrequentContactListFragmentListener(); 141 142 private boolean mSearchInitiated; 143 144 private ContactListFilterController mContactListFilterController; 145 146 private ContactsUnavailableFragment mContactsUnavailableFragment; 147 private ProviderStatusLoader mProviderStatusLoader; 148 private int mProviderStatus = -1; 149 150 private boolean mOptionsMenuContactsAvailable; 151 152 /** 153 * Showing a list of Contacts. Also used for showing search results in search mode. 154 */ 155 private DefaultContactBrowseListFragment mAllFragment; 156 private StrequentContactListFragment mFavoritesFragment; 157 private StrequentContactListFragment mFrequentFragment; 158 private GroupBrowseListFragment mGroupsFragment; 159 160 private View mFavoritesView; 161 private View mBrowserView; 162 private View mDetailsView; 163 164 private View mAddGroupImageView; 165 166 private ContactDetailLayoutController mContactDetailLayoutController; 167 168 private final Handler mHandler = new Handler(); 169 170 /** 171 * TODO: Use ViewPager so that tabs can be swiped left and right. Figure out how to use the 172 * support library in our app. 173 */ 174 private final TabListener mTabListener = new TabListener() { 175 @Override 176 public void onTabUnselected(Tab tab, FragmentTransaction ft) { 177 hideFragmentOnTabUnselect((TabState) tab.getTag(), ft); 178 } 179 180 @Override 181 public void onTabSelected(Tab tab, FragmentTransaction ft) { 182 final TabState tabState = (TabState) tab.getTag(); 183 setSelectedTab(tabState); 184 showFragmentOnTabSelect(tabState, ft); 185 invalidateOptionsMenu(); 186 } 187 188 @Override 189 public void onTabReselected(Tab tab, FragmentTransaction ft) { 190 } 191 }; 192 193 private enum TabState { 194 FAVORITES, ALL, GROUPS 195 } 196 197 private TabState mSelectedTab; 198 199 public PeopleActivity() { 200 mIntentResolver = new ContactsIntentResolver(this); 201 mContactListFilterController = new ContactListFilterController(this); 202 mContactListFilterController.addListener(this); 203 mProviderStatusLoader = new ProviderStatusLoader(this); 204 } 205 206 public boolean areContactsAvailable() { 207 return mProviderStatus == ProviderStatus.STATUS_NORMAL; 208 } 209 210 /** 211 * Initialize fragments that are (or may not be) in the layout. 212 * 213 * For the fragments that are in the layout, we initialize them in 214 * {@link #configureContentView(boolean, Bundle)} after inflating the layout. 215 * 216 * However, there are special fragments which may not be in the layout, so we have to do the 217 * initialization here. 218 * The target fragments are: 219 * - {@link ContactDetailFragment} and {@link ContactDetailUpdatesFragment}: They may not be 220 * in the layout depending on the configuration. (i.e. portrait) 221 * - {@link ContactsUnavailableFragment}: We always create it at runtime. 222 */ 223 @Override 224 public void onAttachFragment(Fragment fragment) { 225 if (fragment instanceof ContactDetailFragment) { 226 mContactDetailFragment = (ContactDetailFragment) fragment; 227 mContactDetailFragment.setListener(mContactDetailFragmentListener); 228 } else if (fragment instanceof ContactDetailUpdatesFragment) { 229 mContactDetailUpdatesFragment = (ContactDetailUpdatesFragment) fragment; 230 } else if (fragment instanceof ContactsUnavailableFragment) { 231 mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment; 232 mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader); 233 mContactsUnavailableFragment.setOnContactsUnavailableActionListener( 234 new ContactsUnavailableFragmentListener()); 235 } 236 } 237 238 @Override 239 protected void onCreate(Bundle savedState) { 240 super.onCreate(savedState); 241 242 configureContentView(true, savedState); 243 } 244 245 @Override 246 protected void onNewIntent(Intent intent) { 247 setIntent(intent); 248 configureContentView(false, null); 249 } 250 251 private void configureContentView(boolean createContentView, Bundle savedState) { 252 // Extract relevant information from the intent 253 mRequest = mIntentResolver.resolveIntent(getIntent()); 254 if (!mRequest.isValid()) { 255 setResult(RESULT_CANCELED); 256 finish(); 257 return; 258 } 259 260 Intent redirect = mRequest.getRedirectIntent(); 261 if (redirect != null) { 262 // Need to start a different activity 263 startActivity(redirect); 264 finish(); 265 return; 266 } 267 268 if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT 269 && !PhoneCapabilityTester.isUsingTwoPanes(this)) { 270 redirect = new Intent(this, ContactDetailActivity.class); 271 redirect.setAction(Intent.ACTION_VIEW); 272 redirect.setData(mRequest.getContactUri()); 273 startActivity(redirect); 274 finish(); 275 return; 276 } 277 278 if (createContentView) { 279 setContentView(R.layout.people_activity); 280 281 final FragmentManager fragmentManager = getFragmentManager(); 282 283 // Hide all tabs (the current tab will later be reshown once a tab is selected) 284 final FragmentTransaction transaction = fragmentManager.beginTransaction(); 285 286 // Common fragments that exist on both 1 and 2 panes. 287 mFavoritesFragment = getFragment(R.id.favorites_fragment); 288 mFavoritesFragment.setListener(mFavoritesFragmentListener); 289 mFavoritesFragment.setDisplayType(DisplayType.STARRED_ONLY); 290 291 mAllFragment = getFragment(R.id.all_fragment); 292 mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener()); 293 if (!getWindow().hasFeature(Window.FEATURE_ACTION_BAR)) { 294 mAllFragment.setContextMenuAdapter( 295 new ContactBrowseListContextMenuAdapter(mAllFragment)); 296 } 297 298 mGroupsFragment = getFragment(R.id.groups_fragment); 299 mGroupsFragment.setListener(new GroupBrowserActionListener()); 300 301 transaction.hide(mAllFragment); 302 transaction.hide(mGroupsFragment); 303 304 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 305 mFavoritesFragment.setQuickContact(true); 306 307 // Container views for fragments 308 mFavoritesView = getView(R.id.favorites_view); 309 mDetailsView = getView(R.id.details_view); 310 mBrowserView = getView(R.id.browse_view); 311 312 // 2-pane only fragments 313 mFrequentFragment = getFragment(R.id.frequent_fragment); 314 mFrequentFragment.setListener(mFavoritesFragmentListener); 315 mFrequentFragment.setDisplayType(DisplayType.FREQUENT_ONLY); 316 mFrequentFragment.setQuickContact(true); 317 318 mContactDetailLoaderFragment = getFragment(R.id.contact_detail_loader_fragment); 319 mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener); 320 321 mGroupDetailFragment = getFragment(R.id.group_detail_fragment); 322 mGroupDetailFragment.setListener(mGroupDetailFragmentListener); 323 mGroupDetailFragment.setQuickContact(true); 324 325 transaction.hide(mContactDetailFragment); 326 transaction.hide(mGroupDetailFragment); 327 } 328 transaction.commit(); 329 fragmentManager.executePendingTransactions(); 330 } 331 332 setTitle(mRequest.getActivityTitle()); 333 ActionBar actionBar = getActionBar(); 334 mActionBarAdapter = new ActionBarAdapter(this, this); 335 mActionBarAdapter.onCreate(savedState, mRequest, getActionBar()); 336 337 ViewPager viewPager = (ViewPager) findViewById(R.id.pager); 338 ContactDetailTabCarousel tabCarousel = (ContactDetailTabCarousel) 339 findViewById(R.id.tab_carousel); 340 mContactDetailLayoutController = new ContactDetailLayoutController( 341 getFragmentManager(), viewPager, tabCarousel, 342 mContactDetailFragmentListener); 343 344 if (createContentView) { 345 actionBar.removeAllTabs(); 346 Tab favoritesTab = actionBar.newTab(); 347 favoritesTab.setTag(TabState.FAVORITES); 348 favoritesTab.setText(getString(R.string.contactsFavoritesLabel)); 349 favoritesTab.setTabListener(mTabListener); 350 actionBar.addTab(favoritesTab); 351 352 Tab allTab = actionBar.newTab(); 353 allTab.setTag(TabState.ALL); 354 allTab.setText(getString(R.string.contactsAllLabel)); 355 allTab.setTabListener(mTabListener); 356 actionBar.addTab(allTab); 357 358 Tab groupsTab = actionBar.newTab(); 359 groupsTab.setTag(TabState.GROUPS); 360 groupsTab.setText(getString(R.string.contactsGroupsLabel)); 361 groupsTab.setTabListener(mTabListener); 362 actionBar.addTab(groupsTab); 363 actionBar.setDisplayShowTitleEnabled(true); 364 365 TypedArray a = obtainStyledAttributes(null, R.styleable.ActionBarHomeIcon); 366 boolean showHomeIcon = a.getBoolean(R.styleable.ActionBarHomeIcon_show_home_icon, true); 367 actionBar.setDisplayShowHomeEnabled(showHomeIcon); 368 369 invalidateOptionsMenuIfNeeded(); 370 } 371 372 configureFragments(savedState == null); 373 } 374 375 private void hideFragmentOnTabUnselect(TabState newTabState, FragmentTransaction ft) { 376 switch (newTabState) { 377 case FAVORITES: { 378 ft.hide(mFavoritesFragment); 379 if (mFrequentFragment != null) { 380 ft.hide(mFrequentFragment); 381 } 382 break; 383 } 384 case ALL: { 385 ft.hide(mAllFragment); 386 if (mContactDetailFragment != null) { 387 ft.hide(mContactDetailFragment); 388 } 389 break; 390 } 391 case GROUPS: { 392 ft.hide(mGroupsFragment); 393 if (mGroupDetailFragment != null) { 394 ft.hide(mGroupDetailFragment); 395 } 396 break; 397 } 398 default: { 399 throw new IllegalStateException("Unexpected tab state: " + newTabState); 400 } 401 } 402 } 403 404 private void showFragmentOnTabSelect(TabState newTabState, FragmentTransaction ft) { 405 switch (newTabState) { 406 case FAVORITES: { 407 ft.show(mFavoritesFragment); 408 if (mFrequentFragment != null) { 409 ft.show(mFrequentFragment); 410 } 411 break; 412 } 413 case ALL: { 414 ft.show(mAllFragment); 415 if (mContactDetailFragment != null) { 416 ft.show(mContactDetailFragment); 417 } 418 break; 419 } 420 case GROUPS: { 421 ft.show(mGroupsFragment); 422 if (mGroupDetailFragment != null) { 423 ft.show(mGroupDetailFragment); 424 } 425 break; 426 } 427 default: { 428 throw new IllegalStateException("Unexpected tab state: " + newTabState); 429 } 430 } 431 } 432 433 private void setSelectedTab(TabState tab) { 434 mSelectedTab = tab; 435 436 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 437 switch (mSelectedTab) { 438 case FAVORITES: 439 mFavoritesView.setVisibility(View.VISIBLE); 440 mBrowserView.setVisibility(View.GONE); 441 mDetailsView.setVisibility(View.GONE); 442 break; 443 case GROUPS: 444 case ALL: 445 mFavoritesView.setVisibility(View.GONE); 446 mBrowserView.setVisibility(View.VISIBLE); 447 mDetailsView.setVisibility(View.VISIBLE); 448 break; 449 } 450 } 451 } 452 453 @Override 454 protected void onPause() { 455 if (mActionBarAdapter != null) { 456 mActionBarAdapter.setListener(null); 457 } 458 459 mOptionsMenuContactsAvailable = false; 460 461 mProviderStatus = -1; 462 mProviderStatusLoader.setProviderStatusListener(null); 463 super.onPause(); 464 } 465 466 @Override 467 protected void onResume() { 468 super.onResume(); 469 if (mActionBarAdapter != null) { 470 mActionBarAdapter.setListener(this); 471 } 472 mProviderStatusLoader.setProviderStatusListener(this); 473 updateFragmentVisibility(); 474 } 475 476 @Override 477 protected void onStart() { 478 mContactListFilterController.onStart(); 479 super.onStart(); 480 } 481 482 private void configureFragments(boolean fromRequest) { 483 if (fromRequest) { 484 ContactListFilter filter = null; 485 int actionCode = mRequest.getActionCode(); 486 switch (actionCode) { 487 case ContactsRequest.ACTION_ALL_CONTACTS: 488 filter = ContactListFilter.createFilterWithType( 489 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 490 break; 491 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES: 492 filter = ContactListFilter.createFilterWithType( 493 ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY); 494 break; 495 496 // TODO: handle FREQUENT and STREQUENT according to the spec 497 case ContactsRequest.ACTION_FREQUENT: 498 case ContactsRequest.ACTION_STREQUENT: 499 // For now they are treated the same as STARRED 500 case ContactsRequest.ACTION_STARRED: 501 filter = ContactListFilter.createFilterWithType( 502 ContactListFilter.FILTER_TYPE_STARRED); 503 break; 504 case ContactsRequest.ACTION_VIEW_CONTACT: 505 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 506 getActionBar().setSelectedNavigationItem(TabState.ALL.ordinal()); 507 } 508 } 509 510 mSearchMode = mRequest.isSearchMode(); 511 if (filter != null) { 512 mContactListFilterController.setContactListFilter(filter, false); 513 mSearchMode = false; 514 } else if (mRequest.getActionCode() == ContactsRequest.ACTION_ALL_CONTACTS) { 515 mContactListFilterController.setContactListFilter( 516 ContactListFilter.createFilterWithType( 517 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS), false); 518 } 519 520 if (mRequest.getContactUri() != null) { 521 mSearchMode = false; 522 } 523 524 mAllFragment.setContactsRequest(mRequest); 525 configureContactListFragmentForRequest(); 526 527 } else { 528 mSearchMode = mActionBarAdapter.isSearchMode(); 529 } 530 531 configureContactListFragment(); 532 configureGroupListFragment(); 533 534 invalidateOptionsMenuIfNeeded(); 535 } 536 537 @Override 538 public void onContactListFilterChanged() { 539 if (mAllFragment == null || !mAllFragment.isAdded()) { 540 return; 541 } 542 543 mAllFragment.setFilter(mContactListFilterController.getFilter()); 544 545 invalidateOptionsMenuIfNeeded(); 546 } 547 548 private void setupContactDetailFragment(final Uri contactLookupUri) { 549 mContactDetailLoaderFragment.loadUri(contactLookupUri); 550 invalidateOptionsMenuIfNeeded(); 551 } 552 553 private void setupGroupDetailFragment(Uri groupUri) { 554 mGroupDetailFragment.loadGroup(groupUri); 555 invalidateOptionsMenuIfNeeded(); 556 } 557 558 /** 559 * Handler for action bar actions. 560 */ 561 @Override 562 public void onAction(Action action) { 563 switch (action) { 564 case START_SEARCH_MODE: 565 // Checking if multi fragments are being displayed 566 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 567 mFavoritesView.setVisibility(View.GONE); 568 mBrowserView.setVisibility(View.VISIBLE); 569 mDetailsView.setVisibility(View.VISIBLE); 570 } 571 // Bring the contact list fragment (and detail fragment if applicable) to the front 572 FragmentTransaction ft = getFragmentManager().beginTransaction(); 573 ft.show(mAllFragment); 574 if (mContactDetailFragment != null) ft.show(mContactDetailFragment); 575 ft.commit(); 576 clearSearch(); 577 break; 578 case STOP_SEARCH_MODE: 579 // Refresh the fragments because search mode was using them to display search 580 // results. 581 clearSearch(); 582 583 // If the last selected tab was not the "All contacts" tab, then hide these 584 // fragments because we need to show favorites or groups. 585 if (mSelectedTab != null && !mSelectedTab.equals(TabState.ALL)) { 586 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 587 transaction.hide(mAllFragment); 588 if (mContactDetailFragment != null) transaction.hide(mContactDetailFragment); 589 transaction.commit(); 590 } 591 if (mSelectedTab != null) setSelectedTab(mSelectedTab); 592 break; 593 case CHANGE_SEARCH_QUERY: 594 loadSearch(mActionBarAdapter.getQueryString()); 595 break; 596 default: 597 throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action); 598 } 599 } 600 601 private void clearSearch() { 602 loadSearch(""); 603 } 604 605 private void loadSearch(String query) { 606 configureFragments(false /* from request */); 607 mAllFragment.setQueryString(query, true); 608 } 609 610 private void configureContactListFragmentForRequest() { 611 Uri contactUri = mRequest.getContactUri(); 612 if (contactUri != null) { 613 mAllFragment.setSelectedContactUri(contactUri); 614 } 615 616 mAllFragment.setSearchMode(mRequest.isSearchMode()); 617 mAllFragment.setQueryString(mRequest.getQueryString(), false); 618 619 if (mRequest.isDirectorySearchEnabled()) { 620 mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT); 621 } else { 622 mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); 623 } 624 625 if (mContactListFilterController.isInitialized()) { 626 mAllFragment.setFilter(mContactListFilterController.getFilter()); 627 } 628 } 629 630 private void configureContactListFragment() { 631 mAllFragment.setSearchMode(mSearchMode); 632 633 final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); 634 mAllFragment.setVisibleScrollbarEnabled(!mSearchMode); 635 mAllFragment.setVerticalScrollbarPosition( 636 useTwoPane 637 ? View.SCROLLBAR_POSITION_LEFT 638 : View.SCROLLBAR_POSITION_RIGHT); 639 mAllFragment.setSelectionVisible(useTwoPane); 640 mAllFragment.setQuickContactEnabled(!useTwoPane); 641 } 642 643 private void configureGroupListFragment() { 644 final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); 645 mGroupsFragment.setVerticalScrollbarPosition( 646 useTwoPane 647 ? View.SCROLLBAR_POSITION_LEFT 648 : View.SCROLLBAR_POSITION_RIGHT); 649 mGroupsFragment.setSelectionVisible(useTwoPane); 650 } 651 652 @Override 653 public void onProviderStatusChange() { 654 updateFragmentVisibility(); 655 } 656 657 private void updateFragmentVisibility() { 658 int providerStatus = mProviderStatusLoader.getProviderStatus(); 659 if (providerStatus == mProviderStatus) { 660 return; 661 } 662 663 mProviderStatus = providerStatus; 664 665 View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view); 666 View mainView = findViewById(R.id.main_view); 667 668 if (mProviderStatus == ProviderStatus.STATUS_NORMAL) { 669 contactsUnavailableView.setVisibility(View.GONE); 670 if (mainView != null) { 671 mainView.setVisibility(View.VISIBLE); 672 } 673 if (mAllFragment != null) { 674 mAllFragment.setEnabled(true); 675 } 676 } else { 677 if (mAllFragment != null) { 678 mAllFragment.setEnabled(false); 679 } 680 if (mContactsUnavailableFragment == null) { 681 mContactsUnavailableFragment = new ContactsUnavailableFragment(); 682 mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader); 683 mContactsUnavailableFragment.setOnContactsUnavailableActionListener( 684 new ContactsUnavailableFragmentListener()); 685 getFragmentManager().beginTransaction() 686 .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment) 687 .commit(); 688 } else { 689 mContactsUnavailableFragment.update(); 690 } 691 contactsUnavailableView.setVisibility(View.VISIBLE); 692 if (mainView != null) { 693 mainView.setVisibility(View.INVISIBLE); 694 } 695 } 696 697 invalidateOptionsMenuIfNeeded(); 698 } 699 700 private final class ContactBrowserActionListener implements OnContactBrowserActionListener { 701 702 @Override 703 public void onSelectionChange() { 704 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 705 setupContactDetailFragment(mAllFragment.getSelectedContactUri()); 706 } 707 } 708 709 @Override 710 public void onViewContactAction(Uri contactLookupUri) { 711 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 712 setupContactDetailFragment(contactLookupUri); 713 } else { 714 startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri)); 715 } 716 } 717 718 @Override 719 public void onCreateNewContactAction() { 720 Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 721 Bundle extras = getIntent().getExtras(); 722 if (extras != null) { 723 intent.putExtras(extras); 724 } 725 startActivity(intent); 726 } 727 728 @Override 729 public void onEditContactAction(Uri contactLookupUri) { 730 Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri); 731 Bundle extras = getIntent().getExtras(); 732 if (extras != null) { 733 intent.putExtras(extras); 734 } 735 startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT); 736 } 737 738 @Override 739 public void onAddToFavoritesAction(Uri contactUri) { 740 ContentValues values = new ContentValues(1); 741 values.put(Contacts.STARRED, 1); 742 getContentResolver().update(contactUri, values, null, null); 743 } 744 745 @Override 746 public void onRemoveFromFavoritesAction(Uri contactUri) { 747 ContentValues values = new ContentValues(1); 748 values.put(Contacts.STARRED, 0); 749 getContentResolver().update(contactUri, values, null, null); 750 } 751 752 @Override 753 public void onCallContactAction(Uri contactUri) { 754 PhoneNumberInteraction.startInteractionForPhoneCall(PeopleActivity.this, contactUri); 755 } 756 757 @Override 758 public void onSmsContactAction(Uri contactUri) { 759 PhoneNumberInteraction.startInteractionForTextMessage(PeopleActivity.this, contactUri); 760 } 761 762 @Override 763 public void onDeleteContactAction(Uri contactUri) { 764 ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); 765 } 766 767 @Override 768 public void onFinishAction() { 769 onBackPressed(); 770 } 771 772 @Override 773 public void onInvalidSelection() { 774 ContactListFilter filter; 775 ContactListFilter currentFilter = mAllFragment.getFilter(); 776 if (currentFilter != null 777 && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 778 filter = ContactListFilter.createFilterWithType( 779 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 780 mAllFragment.setFilter(filter); 781 } else { 782 filter = ContactListFilter.createFilterWithType( 783 ContactListFilter.FILTER_TYPE_SINGLE_CONTACT); 784 mAllFragment.setFilter(filter, false); 785 } 786 mContactListFilterController.setContactListFilter(filter, true); 787 } 788 } 789 790 private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener { 791 @Override 792 public void onContactNotFound() { 793 // Nothing needs to be done here 794 } 795 796 @Override 797 public void onDetailsLoaded(final ContactLoader.Result result) { 798 if (result == null) { 799 return; 800 } 801 // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the 802 // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler} 803 // on the main thread to execute later. 804 mHandler.post(new Runnable() { 805 @Override 806 public void run() { 807 if (!mContactDetailLayoutController.isInitialized()) { 808 mContactDetailLayoutController.setContactDetailFragment( 809 mContactDetailFragment); 810 mContactDetailLayoutController.setContactDetailUpdatesFragment( 811 mContactDetailUpdatesFragment); 812 mContactDetailLayoutController.initialize(); 813 } 814 mContactDetailLayoutController.setContactData(result); 815 } 816 }); 817 } 818 819 @Override 820 public void onEditRequested(Uri contactLookupUri) { 821 startActivityForResult( 822 new Intent(Intent.ACTION_EDIT, contactLookupUri), SUBACTIVITY_EDIT_CONTACT); 823 } 824 825 @Override 826 public void onDeleteRequested(Uri contactUri) { 827 ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); 828 } 829 } 830 831 public class ContactDetailFragmentListener implements ContactDetailFragment.Listener { 832 @Override 833 public void onItemClicked(Intent intent) { 834 try { 835 startActivity(intent); 836 } catch (ActivityNotFoundException e) { 837 Log.e(TAG, "No activity found for intent: " + intent); 838 } 839 } 840 841 @Override 842 public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account) { 843 Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy, 844 Toast.LENGTH_LONG).show(); 845 Intent serviceIntent = ContactSaveService.createNewRawContactIntent( 846 PeopleActivity.this, values, account, 847 PeopleActivity.class, Intent.ACTION_VIEW); 848 startService(serviceIntent); 849 } 850 } 851 852 private class ContactsUnavailableFragmentListener 853 implements OnContactsUnavailableActionListener { 854 855 @Override 856 public void onCreateNewContactAction() { 857 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 858 } 859 860 @Override 861 public void onAddAccountAction() { 862 Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); 863 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 864 intent.putExtra(Settings.EXTRA_AUTHORITIES, 865 new String[] { ContactsContract.AUTHORITY }); 866 startActivity(intent); 867 } 868 869 @Override 870 public void onImportContactsFromFileAction() { 871 AccountSelectionUtil.doImportFromSdCard(PeopleActivity.this, null); 872 } 873 874 @Override 875 public void onFreeInternalStorageAction() { 876 startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)); 877 } 878 } 879 880 private final class StrequentContactListFragmentListener 881 implements StrequentContactListFragment.Listener { 882 @Override 883 public void onContactSelected(Uri contactUri) { 884 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 885 setupContactDetailFragment(contactUri); 886 } else { 887 startActivity(new Intent(Intent.ACTION_VIEW, contactUri)); 888 } 889 } 890 } 891 892 private final class GroupBrowserActionListener implements OnGroupBrowserActionListener { 893 894 @Override 895 public void onViewGroupAction(Uri groupUri) { 896 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 897 setupGroupDetailFragment(groupUri); 898 } else { 899 Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class); 900 intent.setData(groupUri); 901 startActivity(intent); 902 } 903 } 904 } 905 906 private class GroupDetailFragmentListener implements GroupDetailFragment.Listener { 907 @Override 908 public void onGroupSizeUpdated(String size) { 909 // Nothing needs to be done here because the size will be displayed in the detail 910 // fragment 911 } 912 913 @Override 914 public void onGroupTitleUpdated(String title) { 915 // Nothing needs to be done here because the title will be displayed in the detail 916 // fragment 917 } 918 919 @Override 920 public void onEditRequested(Uri groupUri) { 921 final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); 922 intent.setData(groupUri); 923 intent.setAction(Intent.ACTION_EDIT); 924 startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP); 925 } 926 } 927 928 public void startActivityAndForwardResult(final Intent intent) { 929 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 930 931 // Forward extras to the new activity 932 Bundle extras = getIntent().getExtras(); 933 if (extras != null) { 934 intent.putExtras(extras); 935 } 936 startActivity(intent); 937 finish(); 938 } 939 940 @Override 941 public boolean onCreatePanelMenu(int featureId, Menu menu) { 942 // No menu if contacts are unavailable 943 if (!areContactsAvailable()) { 944 return false; 945 } 946 947 return super.onCreatePanelMenu(featureId, menu); 948 } 949 950 @Override 951 public boolean onPreparePanel(int featureId, View view, Menu menu) { 952 // No menu if contacts are unavailable 953 if (!areContactsAvailable()) { 954 return false; 955 } 956 957 return super.onPreparePanel(featureId, view, menu); 958 } 959 960 @Override 961 public boolean onCreateOptionsMenu(Menu menu) { 962 if (!areContactsAvailable()) { 963 return false; 964 } 965 super.onCreateOptionsMenu(menu); 966 967 MenuInflater inflater = getMenuInflater(); 968 inflater.inflate(R.menu.actions, menu); 969 970 // On narrow screens we specify a NEW group button in the {@link ActionBar}, so that 971 // it can be in the overflow menu. On wide screens, we use a custom view because we need 972 // its location for anchoring the account-selector popup. 973 final MenuItem addGroup = menu.findItem(R.id.menu_custom_add_group); 974 if (addGroup != null) { 975 mAddGroupImageView = getLayoutInflater().inflate( 976 R.layout.add_group_menu_item, null, false); 977 View item = mAddGroupImageView.findViewById(R.id.menu_item); 978 item.setOnClickListener(new OnClickListener() { 979 @Override 980 public void onClick(View v) { 981 createNewGroupWithAccountDisambiguation(); 982 } 983 }); 984 addGroup.setActionView(mAddGroupImageView); 985 } 986 return true; 987 } 988 989 private void invalidateOptionsMenuIfNeeded() { 990 if (isOptionsMenuChanged()) { 991 invalidateOptionsMenu(); 992 } 993 } 994 995 public boolean isOptionsMenuChanged() { 996 if (mOptionsMenuContactsAvailable != areContactsAvailable()) { 997 return true; 998 } 999 1000 if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) { 1001 return true; 1002 } 1003 1004 if (mContactDetailLoaderFragment != null && 1005 mContactDetailLoaderFragment.isOptionsMenuChanged()) { 1006 return true; 1007 } 1008 1009 if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) { 1010 return true; 1011 } 1012 1013 return false; 1014 } 1015 1016 @Override 1017 public boolean onPrepareOptionsMenu(Menu menu) { 1018 mOptionsMenuContactsAvailable = areContactsAvailable(); 1019 if (!mOptionsMenuContactsAvailable) { 1020 return false; 1021 } 1022 1023 final MenuItem searchMenu = menu.findItem(R.id.menu_search); 1024 final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact); 1025 MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group); 1026 if (addGroupMenu == null) { 1027 addGroupMenu = menu.findItem(R.id.menu_custom_add_group); 1028 } 1029 1030 if (mActionBarAdapter.isSearchMode()) { 1031 addContactMenu.setVisible(false); 1032 addGroupMenu.setVisible(false); 1033 if (searchMenu != null) { 1034 searchMenu.setVisible(false); // Don't show the search menu in search mode. 1035 } 1036 } else { 1037 switch (mSelectedTab) { 1038 case FAVORITES: 1039 // TODO: Fall through until we determine what the menu items should be for 1040 // this tab 1041 case ALL: 1042 addContactMenu.setVisible(true); 1043 addGroupMenu.setVisible(false); 1044 break; 1045 case GROUPS: 1046 addContactMenu.setVisible(false); 1047 addGroupMenu.setVisible(true); 1048 break; 1049 } 1050 } 1051 1052 MenuItem settings = menu.findItem(R.id.menu_settings); 1053 if (settings != null) { 1054 settings.setVisible(!ContactsPreferenceActivity.isEmpty(this)); 1055 } 1056 1057 return true; 1058 } 1059 1060 @Override 1061 public boolean onOptionsItemSelected(MenuItem item) { 1062 switch (item.getItemId()) { 1063 case R.id.menu_settings: { 1064 final Intent intent = new Intent(this, ContactsPreferenceActivity.class); 1065 startActivity(intent); 1066 return true; 1067 } 1068 case R.id.menu_contacts_filter: { 1069 final Intent intent = new Intent(this, AccountFilterActivity.class); 1070 startActivityForResult(intent, SUBACTIVITY_ACCOUNT_FILTER); 1071 return true; 1072 } 1073 case R.id.menu_search: { 1074 onSearchRequested(); 1075 return true; 1076 } 1077 case R.id.menu_add_contact: { 1078 final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 1079 startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT); 1080 return true; 1081 } 1082 case R.id.menu_add_group: { 1083 createNewGroupWithAccountDisambiguation(); 1084 return true; 1085 } 1086 case R.id.menu_import_export: { 1087 ImportExportDialogFragment.show(getFragmentManager()); 1088 return true; 1089 } 1090 case R.id.menu_accounts: { 1091 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); 1092 intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] { 1093 ContactsContract.AUTHORITY 1094 }); 1095 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1096 startActivity(intent); 1097 return true; 1098 } 1099 } 1100 return false; 1101 } 1102 1103 private void createNewGroupWithAccountDisambiguation() { 1104 final ArrayList<Account> accounts = 1105 AccountTypeManager.getInstance(this).getAccounts(true); 1106 if (accounts.size() <= 1 || mAddGroupImageView == null) { 1107 // No account to choose or no control to anchor the popup-menu to 1108 // ==> just go straight to the editor which will disambig if necessary 1109 final Intent intent = new Intent(this, GroupEditorActivity.class); 1110 intent.setAction(Intent.ACTION_INSERT); 1111 startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); 1112 return; 1113 } 1114 1115 final ListPopupWindow popup = new ListPopupWindow(this, null); 1116 popup.setWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width)); 1117 popup.setAnchorView(mAddGroupImageView); 1118 // Create a list adapter with all writeable accounts (assume that the writeable accounts all 1119 // allow group creation). 1120 final AccountsListAdapter adapter = new AccountsListAdapter(this, true); 1121 popup.setAdapter(adapter); 1122 popup.setOnItemClickListener(new OnItemClickListener() { 1123 @Override 1124 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1125 popup.dismiss(); 1126 final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); 1127 intent.setAction(Intent.ACTION_INSERT); 1128 intent.putExtra(Intents.Insert.ACCOUNT, adapter.getItem(position)); 1129 startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); 1130 } 1131 }); 1132 popup.setModal(true); 1133 popup.show(); 1134 } 1135 1136 @Override 1137 public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, 1138 boolean globalSearch) { 1139 if (mAllFragment != null && mAllFragment.isAdded() && !globalSearch) { 1140 mAllFragment.startSearch(initialQuery); 1141 } else { 1142 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch); 1143 } 1144 } 1145 1146 @Override 1147 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1148 switch (requestCode) { 1149 case SUBACTIVITY_ACCOUNT_FILTER: { 1150 if (resultCode == Activity.RESULT_OK) { 1151 ContactListFilter filter = (ContactListFilter) data.getParcelableExtra( 1152 AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER); 1153 if (filter == null) { 1154 return; 1155 } 1156 // If this is a custom filter, launch the activity to customize the display list 1157 if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) { 1158 final Intent intent = new Intent(this, 1159 CustomContactListFilterActivity.class); 1160 startActivityForResult(intent, SUBACTIVITY_CUSTOMIZE_FILTER); 1161 } else { 1162 mContactListFilterController.setContactListFilter(filter, true); 1163 } 1164 } 1165 break; 1166 } 1167 case SUBACTIVITY_CUSTOMIZE_FILTER: { 1168 if (resultCode == Activity.RESULT_OK) { 1169 mContactListFilterController.selectCustomFilter(); 1170 } 1171 break; 1172 } 1173 case SUBACTIVITY_EDIT_CONTACT: 1174 case SUBACTIVITY_NEW_CONTACT: { 1175 if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) { 1176 mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT); 1177 mAllFragment.reloadDataAndSetSelectedUri(data.getData()); 1178 } 1179 break; 1180 } 1181 1182 case SUBACTIVITY_NEW_GROUP: 1183 case SUBACTIVITY_EDIT_GROUP: { 1184 if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) { 1185 mRequest.setActionCode(ContactsRequest.ACTION_GROUP); 1186 mGroupsFragment.setSelectedUri(data.getData()); 1187 } 1188 break; 1189 } 1190 1191 // TODO: Using the new startActivityWithResultFromFragment API this should not be needed 1192 // anymore 1193 case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER: 1194 if (resultCode == RESULT_OK) { 1195 mAllFragment.onPickerResult(data); 1196 } 1197 1198// TODO fix or remove multipicker code 1199// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) { 1200// // Finish the activity if the sub activity was canceled as back key is used 1201// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES. 1202// finish(); 1203// } 1204// break; 1205 } 1206 } 1207 1208 @Override 1209 public boolean onContextItemSelected(MenuItem item) { 1210 ContextMenuAdapter menuAdapter = mAllFragment.getContextMenuAdapter(); 1211 if (menuAdapter != null) { 1212 return menuAdapter.onContextItemSelected(item); 1213 } 1214 1215 return super.onContextItemSelected(item); 1216 } 1217 1218 @Override 1219 public boolean onKeyDown(int keyCode, KeyEvent event) { 1220 // TODO move to the fragment 1221 switch (keyCode) { 1222// case KeyEvent.KEYCODE_CALL: { 1223// if (callSelection()) { 1224// return true; 1225// } 1226// break; 1227// } 1228 1229 case KeyEvent.KEYCODE_DEL: { 1230 if (deleteSelection()) { 1231 return true; 1232 } 1233 break; 1234 } 1235 default: { 1236 // Bring up the search UI if the user starts typing 1237 final int unicodeChar = event.getUnicodeChar(); 1238 if (unicodeChar != 0 && !Character.isWhitespace(unicodeChar)) { 1239 String query = new String(new int[]{ unicodeChar }, 0, 1); 1240 if (!mActionBarAdapter.isSearchMode()) { 1241 mActionBarAdapter.setQueryString(query); 1242 mActionBarAdapter.setSearchMode(true); 1243 return true; 1244 } else if (!mRequest.isSearchMode()) { 1245 if (!mSearchInitiated) { 1246 mSearchInitiated = true; 1247 startSearch(query, false, null, false); 1248 return true; 1249 } 1250 } 1251 } 1252 } 1253 } 1254 1255 return super.onKeyDown(keyCode, event); 1256 } 1257 1258 @Override 1259 public void onBackPressed() { 1260 if (mSearchMode && mActionBarAdapter != null) { 1261 mActionBarAdapter.setSearchMode(false); 1262 } else { 1263 super.onBackPressed(); 1264 } 1265 } 1266 1267 private boolean deleteSelection() { 1268 // TODO move to the fragment 1269// if (mActionCode == ContactsRequest.ACTION_DEFAULT) { 1270// final int position = mListView.getSelectedItemPosition(); 1271// if (position != ListView.INVALID_POSITION) { 1272// Uri contactUri = getContactUri(position); 1273// if (contactUri != null) { 1274// doContactDelete(contactUri); 1275// return true; 1276// } 1277// } 1278// } 1279 return false; 1280 } 1281 1282 @Override 1283 protected void onSaveInstanceState(Bundle outState) { 1284 super.onSaveInstanceState(outState); 1285 outState.putBoolean(KEY_SEARCH_MODE, mSearchMode); 1286 if (mActionBarAdapter != null) { 1287 mActionBarAdapter.onSaveInstanceState(outState); 1288 } 1289 if (mContactDetailLayoutController != null) { 1290 mContactDetailLayoutController.onSaveInstanceState(outState); 1291 } 1292 } 1293 1294 @Override 1295 protected void onRestoreInstanceState(Bundle inState) { 1296 super.onRestoreInstanceState(inState); 1297 mSearchMode = inState.getBoolean(KEY_SEARCH_MODE); 1298 if (mActionBarAdapter != null) { 1299 mActionBarAdapter.onRestoreInstanceState(inState); 1300 } 1301 if (mContactDetailLayoutController != null) { 1302 mContactDetailLayoutController.onRestoreInstanceState(inState); 1303 } 1304 } 1305 1306 @Override 1307 public DialogManager getDialogManager() { 1308 return mDialogManager; 1309 } 1310 1311 // Visible for testing 1312 public ContactBrowseListFragment getListFragment() { 1313 return mAllFragment; 1314 } 1315 1316 // Visible for testing 1317 public ContactDetailFragment getDetailFragment() { 1318 return mContactDetailFragment; 1319 } 1320} 1321