PeopleActivity.java revision fbac52efa6d07113b6758cf1cb1f19c68c42c7e6
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.activities.ActionBarAdapter.TabState; 24import com.android.contacts.detail.ContactDetailFragment; 25import com.android.contacts.detail.ContactDetailLayoutController; 26import com.android.contacts.detail.ContactDetailTabCarousel; 27import com.android.contacts.detail.ContactDetailUpdatesFragment; 28import com.android.contacts.detail.ContactLoaderFragment; 29import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener; 30import com.android.contacts.group.GroupBrowseListFragment; 31import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener; 32import com.android.contacts.group.GroupDetailFragment; 33import com.android.contacts.interactions.ContactDeletionInteraction; 34import com.android.contacts.interactions.ImportExportDialogFragment; 35import com.android.contacts.interactions.PhoneNumberInteraction; 36import com.android.contacts.list.AccountFilterActivity; 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.ContactTileListFragment; 53import com.android.contacts.model.AccountTypeManager; 54import com.android.contacts.preference.ContactsPreferenceActivity; 55import com.android.contacts.preference.DisplayOptionsPreferenceFragment; 56import com.android.contacts.util.AccountSelectionUtil; 57import com.android.contacts.util.AccountsListAdapter; 58import com.android.contacts.util.DialogManager; 59import com.android.contacts.util.PhoneCapabilityTester; 60 61import android.accounts.Account; 62import android.app.Activity; 63import android.app.Fragment; 64import android.app.FragmentManager; 65import android.app.FragmentTransaction; 66import android.content.ActivityNotFoundException; 67import android.content.ContentValues; 68import android.content.Intent; 69import android.net.Uri; 70import android.os.Bundle; 71import android.os.Handler; 72import android.os.Parcelable; 73import android.preference.PreferenceActivity; 74import android.provider.ContactsContract; 75import android.provider.ContactsContract.Contacts; 76import android.provider.ContactsContract.Intents; 77import android.provider.ContactsContract.ProviderStatus; 78import android.provider.Settings; 79import android.support.v13.app.FragmentPagerAdapter; 80import android.support.v4.view.PagerAdapter; 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.widget.AdapterView; 90import android.widget.AdapterView.OnItemClickListener; 91import android.widget.ListPopupWindow; 92import android.widget.Toast; 93 94import java.util.ArrayList; 95import java.util.concurrent.atomic.AtomicInteger; 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 private static final Boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE 108 109 private static final int SUBACTIVITY_NEW_CONTACT = 2; 110 private static final int SUBACTIVITY_EDIT_CONTACT = 3; 111 private static final int SUBACTIVITY_NEW_GROUP = 4; 112 private static final int SUBACTIVITY_EDIT_GROUP = 5; 113 private static final int SUBACTIVITY_ACCOUNT_FILTER = 6; 114 private static final int SUBACTIVITY_CUSTOMIZE_FILTER = 7; 115 116 private static final String KEY_SEARCH_MODE = "searchMode"; 117 118 private DialogManager mDialogManager = new DialogManager(this); 119 120 private ContactsIntentResolver mIntentResolver; 121 private ContactsRequest mRequest; 122 123 private ActionBarAdapter mActionBarAdapter; 124 125 private ContactDetailFragment mContactDetailFragment; 126 private ContactDetailUpdatesFragment mContactDetailUpdatesFragment; 127 private final ContactDetailFragmentListener mContactDetailFragmentListener = 128 new ContactDetailFragmentListener(); 129 130 private ContactLoaderFragment mContactDetailLoaderFragment; 131 private final ContactDetailLoaderFragmentListener mContactDetailLoaderFragmentListener = 132 new ContactDetailLoaderFragmentListener(); 133 134 private GroupDetailFragment mGroupDetailFragment; 135 private final GroupDetailFragmentListener mGroupDetailFragmentListener = 136 new GroupDetailFragmentListener(); 137 138 private ContactTileListFragment.Listener mFavoritesFragmentListener = 139 new StrequentContactListFragmentListener(); 140 141 private ContactListFilterController mContactListFilterController; 142 143 private ContactsUnavailableFragment mContactsUnavailableFragment; 144 private ProviderStatusLoader mProviderStatusLoader; 145 private int mProviderStatus = -1; 146 147 private boolean mOptionsMenuContactsAvailable; 148 149 /** 150 * Showing a list of Contacts. Also used for showing search results in search mode. 151 */ 152 private DefaultContactBrowseListFragment mAllFragment; 153 private ContactTileListFragment mFavoritesFragment; 154 private ContactTileListFragment mFrequentFragment; 155 private GroupBrowseListFragment mGroupsFragment; 156 157 private View mFavoritesView; 158 private View mBrowserView; 159 private View mDetailsView; 160 161 private View mAddGroupImageView; 162 163 /** ViewPager for swipe, used only on the phone (i.e. one-pane mode) */ 164 private ViewPager mTabPager; 165 private TabPagerAdapter mTabPagerAdapter; 166 167 private ContactDetailLayoutController mContactDetailLayoutController; 168 169 private final Handler mHandler = new Handler(); 170 171 /** 172 * True if this activity instance is a re-created one. i.e. set true after orientation change. 173 * This is set in {@link #onCreate} for later use in {@link #onStart}. 174 */ 175 private boolean mIsRecreatedInstance; 176 177 /** 178 * If {@link #configureFragments(boolean)} is already called. Used to avoid calling it twice 179 * in {@link #onStart}. 180 * (This initialization only needs to be done once in onStart() when the Activity was just 181 * created from scratch -- i.e. onCreate() was just called) 182 */ 183 private boolean mFragmentInitialized; 184 185 /** Sequential ID assigned to each instance; used for logging */ 186 private final int mInstanceId; 187 private static final AtomicInteger sNextInstanceId = new AtomicInteger(); 188 189 public PeopleActivity() { 190 mInstanceId = sNextInstanceId.getAndIncrement(); 191 mIntentResolver = new ContactsIntentResolver(this); 192 mContactListFilterController = new ContactListFilterController(this); 193 mContactListFilterController.addListener(this); 194 mProviderStatusLoader = new ProviderStatusLoader(this); 195 } 196 197 @Override 198 public String toString() { 199 // Shown on logcat 200 return String.format("%s@%d", getClass().getSimpleName(), mInstanceId); 201 } 202 203 public boolean areContactsAvailable() { 204 return mProviderStatus == ProviderStatus.STATUS_NORMAL; 205 } 206 207 /** 208 * Initialize fragments that are (or may not be) in the layout. 209 * 210 * For the fragments that are in the layout, we initialize them in 211 * {@link #createViewsAndFragments(Bundle)} after inflating the layout. 212 * 213 * However, there are special fragments which may not be in the layout, so we have to do the 214 * initialization here. 215 * The target fragments are: 216 * - {@link ContactDetailFragment} and {@link ContactDetailUpdatesFragment}: They may not be 217 * in the layout depending on the configuration. (i.e. portrait) 218 * - {@link ContactsUnavailableFragment}: We always create it at runtime. 219 */ 220 @Override 221 public void onAttachFragment(Fragment fragment) { 222 if (fragment instanceof ContactDetailFragment) { 223 mContactDetailFragment = (ContactDetailFragment) fragment; 224 mContactDetailFragment.setListener(mContactDetailFragmentListener); 225 } else if (fragment instanceof ContactDetailUpdatesFragment) { 226 mContactDetailUpdatesFragment = (ContactDetailUpdatesFragment) fragment; 227 } else if (fragment instanceof ContactsUnavailableFragment) { 228 mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment; 229 mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader); 230 mContactsUnavailableFragment.setOnContactsUnavailableActionListener( 231 new ContactsUnavailableFragmentListener()); 232 } 233 } 234 235 @Override 236 protected void onCreate(Bundle savedState) { 237 super.onCreate(savedState); 238 239 if (!processIntent(false)) { 240 finish(); 241 return; 242 } 243 244 mIsRecreatedInstance = (savedState != null); 245 createViewsAndFragments(savedState); 246 } 247 248 @Override 249 protected void onNewIntent(Intent intent) { 250 setIntent(intent); 251 if (!processIntent(true)) { 252 finish(); 253 return; 254 } 255 mActionBarAdapter.initialize(null, mRequest); 256 257 // Re-configure fragments. 258 configureFragments(true /* from request */); 259 invalidateOptionsMenuIfNeeded(); 260 } 261 262 /** 263 * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect 264 * is needed. 265 * 266 * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}. 267 * @return {@code true} if {@link PeopleActivity} should continue running. {@code false} 268 * if it shouldn't, in which case the caller should finish() itself and shouldn't do 269 * farther initialization. 270 */ 271 private boolean processIntent(boolean forNewIntent) { 272 // Extract relevant information from the intent 273 mRequest = mIntentResolver.resolveIntent(getIntent()); 274 if (DEBUG) { 275 Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent 276 + " intent=" + getIntent() + " request=" + mRequest); 277 } 278 if (!mRequest.isValid()) { 279 setResult(RESULT_CANCELED); 280 return false; 281 } 282 283 Intent redirect = mRequest.getRedirectIntent(); 284 if (redirect != null) { 285 // Need to start a different activity 286 startActivity(redirect); 287 return false; 288 } 289 290 if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT 291 && !PhoneCapabilityTester.isUsingTwoPanes(this)) { 292 redirect = new Intent(this, ContactDetailActivity.class); 293 redirect.setAction(Intent.ACTION_VIEW); 294 redirect.setData(mRequest.getContactUri()); 295 startActivity(redirect); 296 return false; 297 } 298 setTitle(mRequest.getActivityTitle()); 299 return true; 300 } 301 302 private void createViewsAndFragments(Bundle savedState) { 303 setContentView(R.layout.people_activity); 304 305 final FragmentManager fragmentManager = getFragmentManager(); 306 307 // Hide all tabs (the current tab will later be reshown once a tab is selected) 308 final FragmentTransaction transaction = fragmentManager.beginTransaction(); 309 310 // Prepare the fragments which are used both on 1-pane and on 2-pane. 311 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 312 mFavoritesFragment = getFragment(R.id.favorites_fragment); 313 mAllFragment = getFragment(R.id.all_fragment); 314 mGroupsFragment = getFragment(R.id.groups_fragment); 315 } else { 316 mTabPager = getView(R.id.tab_pager); 317 mTabPagerAdapter = new TabPagerAdapter(); 318 mTabPager.setAdapter(mTabPagerAdapter); 319 mTabPager.setOnPageChangeListener(new TabPagerListener()); 320 321 final String FAVORITE_TAG = "tab-pager-favorite"; 322 final String ALL_TAG = "tab-pager-all"; 323 final String GROUPS_TAG = "tab-pager-groups"; 324 325 // Create the fragments and add as children of the view pager. 326 // The pager adapter will only change the visibility; it'll never create/destroy 327 // fragments. 328 // However, if it's after screen rotation, the fragments have been re-created by 329 // the fragment manager, so first see if there're already the target fragments 330 // existing. 331 mFavoritesFragment = (ContactTileListFragment) 332 fragmentManager.findFragmentByTag(FAVORITE_TAG); 333 mAllFragment = (DefaultContactBrowseListFragment) 334 fragmentManager.findFragmentByTag(ALL_TAG); 335 mGroupsFragment = (GroupBrowseListFragment) 336 fragmentManager.findFragmentByTag(GROUPS_TAG); 337 338 if (mFavoritesFragment == null) { 339 mFavoritesFragment = new ContactTileListFragment(); 340 mAllFragment = new DefaultContactBrowseListFragment(); 341 mGroupsFragment = new GroupBrowseListFragment(); 342 343 transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG); 344 transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG); 345 transaction.add(R.id.tab_pager, mGroupsFragment, GROUPS_TAG); 346 } 347 } 348 349 mFavoritesFragment.setListener(mFavoritesFragmentListener); 350 351 mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener()); 352 353 mGroupsFragment.setListener(new GroupBrowserActionListener()); 354 355 // Hide all fragments for now. We adjust visibility when we get onSelectedTabChanged() 356 // from ActionBarAdapter. 357 transaction.hide(mFavoritesFragment); 358 transaction.hide(mAllFragment); 359 transaction.hide(mGroupsFragment); 360 361 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 362 // Prepare 2-pane only fragments/views... 363 364 // Container views for fragments 365 mFavoritesView = getView(R.id.favorites_view); 366 mDetailsView = getView(R.id.details_view); 367 mBrowserView = getView(R.id.browse_view); 368 369 // 2-pane only fragments 370 mFrequentFragment = getFragment(R.id.frequent_fragment); 371 mFrequentFragment.setListener(mFavoritesFragmentListener); 372 mFrequentFragment.setDisplayType(DisplayType.FREQUENT_ONLY); 373 mFrequentFragment.enableQuickContact(true); 374 375 mContactDetailLoaderFragment = getFragment(R.id.contact_detail_loader_fragment); 376 mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener); 377 378 mGroupDetailFragment = getFragment(R.id.group_detail_fragment); 379 mGroupDetailFragment.setListener(mGroupDetailFragmentListener); 380 mGroupDetailFragment.setQuickContact(true); 381 382 transaction.hide(mContactDetailFragment); 383 transaction.hide(mGroupDetailFragment); 384 385 // Configure contact details 386 ViewPager viewPager = (ViewPager) findViewById(R.id.pager); 387 ContactDetailTabCarousel tabCarousel = (ContactDetailTabCarousel) 388 findViewById(R.id.tab_carousel); 389 mContactDetailLayoutController = new ContactDetailLayoutController( 390 getFragmentManager(), viewPager, tabCarousel, 391 mContactDetailFragmentListener); 392 } 393 transaction.commit(); 394 fragmentManager.executePendingTransactions(); 395 396 // Setting Properties after fragment is created 397 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 398 mFavoritesFragment.enableQuickContact(true); 399 mFavoritesFragment.setDisplayType(DisplayType.STARRED_ONLY); 400 } else { 401 mFavoritesFragment.setDisplayType(DisplayType.STREQUENT); 402 } 403 404 // Configure action bar 405 mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar()); 406 mActionBarAdapter.initialize(savedState, mRequest); 407 408 invalidateOptionsMenuIfNeeded(); 409 } 410 411 @Override 412 protected void onStart() { 413 if (!mFragmentInitialized) { 414 mFragmentInitialized = true; 415 /* Configure fragments if we haven't. 416 * 417 * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}. 418 * 419 * However, because this method may indirectly touch views in fragments but fragments 420 * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT 421 * have views until {@link Activity#onCreate} finishes (they would if they were inflated 422 * from a layout), we need to do it here in {@link #onStart()}. 423 * 424 * (When {@link Fragment#onCreateView} is called is different in the former case and 425 * in the latter case, unfortunately.) 426 * 427 * Also, we skip most of the work in it if the activity is a re-created one. 428 * (so the argument.) 429 */ 430 configureFragments(!mIsRecreatedInstance); 431 } 432 mContactListFilterController.onStart(); 433 super.onStart(); 434 } 435 436 @Override 437 protected void onPause() { 438 mOptionsMenuContactsAvailable = false; 439 440 mProviderStatus = -1; 441 mProviderStatusLoader.setProviderStatusListener(null); 442 super.onPause(); 443 } 444 445 @Override 446 protected void onResume() { 447 super.onResume(); 448 mProviderStatusLoader.setProviderStatusListener(this); 449 showContactsUnavailableFragmentIfNecessary(); 450 451 // Re-register the listener, which may have been cleared when onSaveInstanceState was 452 // called. See also: onSaveInstanceState 453 mActionBarAdapter.setListener(this); 454 // Current tab may have changed since the last onSaveInstanceState(). Make sure 455 // the actual contents match the tab. 456 updateFragmentsVisibility(); 457 } 458 459 @Override 460 protected void onDestroy() { 461 // mActionBarAdapter will be null here when redirecting to another activity in 462 // configureContentView(). 463 if (mActionBarAdapter != null) { 464 mActionBarAdapter.setListener(null); 465 } 466 super.onDestroy(); 467 } 468 469 private void configureFragments(boolean fromRequest) { 470 if (fromRequest) { 471 ContactListFilter filter = null; 472 int actionCode = mRequest.getActionCode(); 473 boolean searchMode = mRequest.isSearchMode(); 474 switch (actionCode) { 475 case ContactsRequest.ACTION_ALL_CONTACTS: 476 filter = ContactListFilter.createFilterWithType( 477 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 478 break; 479 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES: 480 filter = ContactListFilter.createFilterWithType( 481 ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY); 482 break; 483 484 // TODO: handle FREQUENT and STREQUENT according to the spec 485 case ContactsRequest.ACTION_FREQUENT: 486 case ContactsRequest.ACTION_STREQUENT: 487 // For now they are treated the same as STARRED 488 case ContactsRequest.ACTION_STARRED: 489 filter = ContactListFilter.createFilterWithType( 490 ContactListFilter.FILTER_TYPE_STARRED); 491 break; 492 case ContactsRequest.ACTION_VIEW_CONTACT: 493 if (PhoneCapabilityTester.isUsingTwoPanes(this)) { 494 mActionBarAdapter.setCurrentTab(TabState.ALL); 495 } 496 } 497 498 if (filter != null) { 499 mContactListFilterController.setContactListFilter(filter, false); 500 searchMode = false; 501 } 502 503 if (mRequest.getContactUri() != null) { 504 searchMode = false; 505 } 506 507 mActionBarAdapter.setSearchMode(searchMode); 508 configureContactListFragmentForRequest(); 509 } 510 511 configureContactListFragment(); 512 configureGroupListFragment(); 513 514 invalidateOptionsMenuIfNeeded(); 515 } 516 517 @Override 518 public void onContactListFilterChanged() { 519 if (mAllFragment == null || !mAllFragment.isAdded()) { 520 return; 521 } 522 523 mAllFragment.setFilter(mContactListFilterController.getFilter()); 524 525 invalidateOptionsMenuIfNeeded(); 526 } 527 528 private void setupContactDetailFragment(final Uri contactLookupUri) { 529 mContactDetailLoaderFragment.loadUri(contactLookupUri); 530 invalidateOptionsMenuIfNeeded(); 531 } 532 533 private void setupGroupDetailFragment(Uri groupUri) { 534 mGroupDetailFragment.loadGroup(groupUri); 535 invalidateOptionsMenuIfNeeded(); 536 } 537 538 /** 539 * Handler for action bar actions. 540 */ 541 @Override 542 public void onAction(Action action) { 543 switch (action) { 544 case START_SEARCH_MODE: 545 // Tell the fragments that we're in the search mode 546 configureFragments(false /* from request */); 547 updateFragmentsVisibility(); 548 invalidateOptionsMenu(); 549 break; 550 case STOP_SEARCH_MODE: 551 clearSearch(); 552 updateFragmentsVisibility(); 553 invalidateOptionsMenu(); 554 break; 555 case CHANGE_SEARCH_QUERY: 556 loadSearch(mActionBarAdapter.getQueryString()); 557 break; 558 default: 559 throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action); 560 } 561 } 562 563 @Override 564 public void onSelectedTabChanged() { 565 updateFragmentsVisibility(); 566 } 567 568 /** 569 * Updates the fragment/view visibility according to the current mode, such as 570 * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}. 571 */ 572 private void updateFragmentsVisibility() { 573 TabState tab = mActionBarAdapter.getCurrentTab(); 574 575 // We use ViewPager on 1-pane. 576 if (!PhoneCapabilityTester.isUsingTwoPanes(this)) { 577 if (mActionBarAdapter.isSearchMode()) { 578 mTabPagerAdapter.setSearchMode(true); 579 } else { 580 mTabPagerAdapter.setSearchMode(false); 581 int tabIndex = tab.ordinal(); 582 if (mTabPager.getCurrentItem() != tabIndex) { 583 mTabPager.setCurrentItem(tab.ordinal(), false /* no smooth scroll */); 584 } 585 } 586 invalidateOptionsMenu(); 587 return; 588 } 589 590 // for the tablet... 591 592 // If in search mode, we use the all list + contact details to show the result. 593 if (mActionBarAdapter.isSearchMode()) { 594 tab = TabState.ALL; 595 } 596 switch (tab) { 597 case FAVORITES: 598 mFavoritesView.setVisibility(View.VISIBLE); 599 mBrowserView.setVisibility(View.GONE); 600 mDetailsView.setVisibility(View.GONE); 601 break; 602 case GROUPS: 603 case ALL: 604 mFavoritesView.setVisibility(View.GONE); 605 mBrowserView.setVisibility(View.VISIBLE); 606 mDetailsView.setVisibility(View.VISIBLE); 607 break; 608 } 609 FragmentManager fragmentManager = getFragmentManager(); 610 FragmentTransaction ft = fragmentManager.beginTransaction(); 611 612 // Note mContactDetailLoaderFragment is an invisible fragment, but we still have to show/ 613 // hide it so its options menu will be shown/hidden. 614 switch (tab) { 615 case FAVORITES: 616 showFragment(ft, mFavoritesFragment); 617 showFragment(ft, mFrequentFragment); 618 hideFragment(ft, mAllFragment); 619 hideFragment(ft, mContactDetailLoaderFragment); 620 hideFragment(ft, mContactDetailFragment); 621 hideFragment(ft, mGroupsFragment); 622 hideFragment(ft, mGroupDetailFragment); 623 break; 624 case ALL: 625 hideFragment(ft, mFavoritesFragment); 626 hideFragment(ft, mFrequentFragment); 627 showFragment(ft, mAllFragment); 628 showFragment(ft, mContactDetailLoaderFragment); 629 showFragment(ft, mContactDetailFragment); 630 hideFragment(ft, mGroupsFragment); 631 hideFragment(ft, mGroupDetailFragment); 632 break; 633 case GROUPS: 634 hideFragment(ft, mFavoritesFragment); 635 hideFragment(ft, mFrequentFragment); 636 hideFragment(ft, mAllFragment); 637 hideFragment(ft, mContactDetailLoaderFragment); 638 hideFragment(ft, mContactDetailFragment); 639 showFragment(ft, mGroupsFragment); 640 showFragment(ft, mGroupDetailFragment); 641 break; 642 } 643 if (!ft.isEmpty()) { 644 ft.commit(); 645 fragmentManager.executePendingTransactions(); 646 // When switching tabs, we need to invalidate options menu, but executing a 647 // fragment transaction does it implicitly. We don't have to call invalidateOptionsMenu 648 // manually. 649 } 650 } 651 652 private class TabPagerListener implements ViewPager.OnPageChangeListener { 653 @Override 654 public void onPageScrollStateChanged(int state) { 655 } 656 657 @Override 658 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 659 } 660 661 @Override 662 public void onPageSelected(int position) { 663 // Make sure not in the search mode, in which case position != TabState.ordinal(). 664 if (!mTabPagerAdapter.isSearchMode()) { 665 mActionBarAdapter.setCurrentTab(TabState.fromInt(position), false); 666 invalidateOptionsMenu(); 667 } 668 } 669 } 670 671 /** 672 * Adapter for the {@link ViewPager}. Unlike {@link FragmentPagerAdapter}, 673 * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/ 674 * {@link #destroyItem} show/hide fragments instead of attaching/detaching. 675 * 676 * In search mode, we always show the "all" fragment, and disable the swipe. We change the 677 * number of items to 1 to disable the swipe. 678 * 679 * TODO figure out a more straight way to disable swipe. 680 */ 681 private class TabPagerAdapter extends PagerAdapter { 682 private final FragmentManager mFragmentManager; 683 private FragmentTransaction mCurTransaction = null; 684 685 private boolean mTabPagerAdapterSearchMode; 686 687 public TabPagerAdapter() { 688 mFragmentManager = getFragmentManager(); 689 } 690 691 public boolean isSearchMode() { 692 return mTabPagerAdapterSearchMode; 693 } 694 695 public void setSearchMode(boolean searchMode) { 696 if (searchMode == mTabPagerAdapterSearchMode) { 697 return; 698 } 699 mTabPagerAdapterSearchMode = searchMode; 700 notifyDataSetChanged(); 701 } 702 703 @Override 704 public int getCount() { 705 return mTabPagerAdapterSearchMode ? 1 : TabState.values().length; 706 } 707 708 /** Gets called when the number of items changes. */ 709 @Override 710 public int getItemPosition(Object object) { 711 if (mTabPagerAdapterSearchMode) { 712 if (object == mAllFragment) { 713 return 0; // Only 1 page in search mode 714 } 715 } else { 716 if (object == mFavoritesFragment) { 717 return TabState.FAVORITES.ordinal(); 718 } 719 if (object == mAllFragment) { 720 return TabState.ALL.ordinal(); 721 } 722 if (object == mGroupsFragment) { 723 return TabState.GROUPS.ordinal(); 724 } 725 } 726 return POSITION_NONE; 727 } 728 729 @Override 730 public void startUpdate(View container) { 731 } 732 733 private Fragment getFragment(int position) { 734 if (mTabPagerAdapterSearchMode) { 735 if (position == 0) { 736 return mAllFragment; 737 } 738 } else { 739 if (position == TabState.FAVORITES.ordinal()) { 740 return mFavoritesFragment; 741 } else if (position == TabState.ALL.ordinal()) { 742 return mAllFragment; 743 } else if (position == TabState.GROUPS.ordinal()) { 744 return mGroupsFragment; 745 } 746 } 747 throw new IllegalArgumentException("position: " + position); 748 } 749 750 @Override 751 public Object instantiateItem(View container, int position) { 752 if (mCurTransaction == null) { 753 mCurTransaction = mFragmentManager.beginTransaction(); 754 } 755 Fragment f = getFragment(position); 756 mCurTransaction.show(f); 757 return f; 758 } 759 760 @Override 761 public void destroyItem(View container, int position, Object object) { 762 if (mCurTransaction == null) { 763 mCurTransaction = mFragmentManager.beginTransaction(); 764 } 765 mCurTransaction.hide((Fragment) object); 766 } 767 768 @Override 769 public void finishUpdate(View container) { 770 if (mCurTransaction != null) { 771 mCurTransaction.commit(); 772 mCurTransaction = null; 773 mFragmentManager.executePendingTransactions(); 774 } 775 } 776 777 @Override 778 public boolean isViewFromObject(View view, Object object) { 779 return ((Fragment) object).getView() == view; 780 } 781 782 @Override 783 public Parcelable saveState() { 784 return null; 785 } 786 787 @Override 788 public void restoreState(Parcelable state, ClassLoader loader) { 789 } 790 } 791 792 private void clearSearch() { 793 loadSearch(""); 794 } 795 796 private void loadSearch(String query) { 797 configureFragments(false /* from request */); 798 mAllFragment.setQueryString(query, true); 799 } 800 801 private void configureContactListFragmentForRequest() { 802 mAllFragment.setContactsRequest(mRequest); 803 804 Uri contactUri = mRequest.getContactUri(); 805 if (contactUri != null) { 806 mAllFragment.setSelectedContactUri(contactUri); 807 } 808 809 mAllFragment.setSearchMode(mActionBarAdapter.isSearchMode()); 810 mAllFragment.setQueryString(mActionBarAdapter.getQueryString(), false); 811 812 if (mRequest.isDirectorySearchEnabled()) { 813 mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT); 814 } else { 815 mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); 816 } 817 818 if (mContactListFilterController.isInitialized()) { 819 mAllFragment.setFilter(mContactListFilterController.getFilter()); 820 } 821 } 822 823 private void configureContactListFragment() { 824 final boolean searchMode = mActionBarAdapter.isSearchMode(); 825 mAllFragment.setSearchMode(searchMode); 826 827 final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); 828 mAllFragment.setVisibleScrollbarEnabled(!searchMode); 829 mAllFragment.setVerticalScrollbarPosition( 830 useTwoPane 831 ? View.SCROLLBAR_POSITION_LEFT 832 : View.SCROLLBAR_POSITION_RIGHT); 833 mAllFragment.setSelectionVisible(useTwoPane); 834 mAllFragment.setQuickContactEnabled(!useTwoPane); 835 } 836 837 private void configureGroupListFragment() { 838 final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); 839 mGroupsFragment.setVerticalScrollbarPosition( 840 useTwoPane 841 ? View.SCROLLBAR_POSITION_LEFT 842 : View.SCROLLBAR_POSITION_RIGHT); 843 mGroupsFragment.setSelectionVisible(useTwoPane); 844 } 845 846 @Override 847 public void onProviderStatusChange() { 848 showContactsUnavailableFragmentIfNecessary(); 849 } 850 851 private void showContactsUnavailableFragmentIfNecessary() { 852 int providerStatus = mProviderStatusLoader.getProviderStatus(); 853 if (providerStatus == mProviderStatus) { 854 return; 855 } 856 857 mProviderStatus = providerStatus; 858 859 View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view); 860 View mainView = findViewById(R.id.main_view); 861 862 if (mProviderStatus == ProviderStatus.STATUS_NORMAL) { 863 contactsUnavailableView.setVisibility(View.GONE); 864 if (mainView != null) { 865 mainView.setVisibility(View.VISIBLE); 866 } 867 if (mAllFragment != null) { 868 mAllFragment.setEnabled(true); 869 } 870 } else { 871 if (mAllFragment != null) { 872 mAllFragment.setEnabled(false); 873 } 874 if (mContactsUnavailableFragment == null) { 875 mContactsUnavailableFragment = new ContactsUnavailableFragment(); 876 mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader); 877 mContactsUnavailableFragment.setOnContactsUnavailableActionListener( 878 new ContactsUnavailableFragmentListener()); 879 getFragmentManager().beginTransaction() 880 .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment) 881 .commit(); 882 } else { 883 mContactsUnavailableFragment.update(); 884 } 885 contactsUnavailableView.setVisibility(View.VISIBLE); 886 if (mainView != null) { 887 mainView.setVisibility(View.INVISIBLE); 888 } 889 } 890 891 invalidateOptionsMenuIfNeeded(); 892 } 893 894 private final class ContactBrowserActionListener implements OnContactBrowserActionListener { 895 896 @Override 897 public void onSelectionChange() { 898 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 899 setupContactDetailFragment(mAllFragment.getSelectedContactUri()); 900 } 901 } 902 903 @Override 904 public void onViewContactAction(Uri contactLookupUri) { 905 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 906 setupContactDetailFragment(contactLookupUri); 907 } else { 908 startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri)); 909 } 910 } 911 912 @Override 913 public void onCreateNewContactAction() { 914 Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 915 Bundle extras = getIntent().getExtras(); 916 if (extras != null) { 917 intent.putExtras(extras); 918 } 919 startActivity(intent); 920 } 921 922 @Override 923 public void onEditContactAction(Uri contactLookupUri) { 924 Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri); 925 Bundle extras = getIntent().getExtras(); 926 if (extras != null) { 927 intent.putExtras(extras); 928 } 929 startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT); 930 } 931 932 @Override 933 public void onAddToFavoritesAction(Uri contactUri) { 934 ContentValues values = new ContentValues(1); 935 values.put(Contacts.STARRED, 1); 936 getContentResolver().update(contactUri, values, null, null); 937 } 938 939 @Override 940 public void onRemoveFromFavoritesAction(Uri contactUri) { 941 ContentValues values = new ContentValues(1); 942 values.put(Contacts.STARRED, 0); 943 getContentResolver().update(contactUri, values, null, null); 944 } 945 946 @Override 947 public void onCallContactAction(Uri contactUri) { 948 PhoneNumberInteraction.startInteractionForPhoneCall(PeopleActivity.this, contactUri); 949 } 950 951 @Override 952 public void onSmsContactAction(Uri contactUri) { 953 PhoneNumberInteraction.startInteractionForTextMessage(PeopleActivity.this, contactUri); 954 } 955 956 @Override 957 public void onDeleteContactAction(Uri contactUri) { 958 ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); 959 } 960 961 @Override 962 public void onFinishAction() { 963 onBackPressed(); 964 } 965 966 @Override 967 public void onInvalidSelection() { 968 ContactListFilter filter; 969 ContactListFilter currentFilter = mAllFragment.getFilter(); 970 if (currentFilter != null 971 && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 972 filter = ContactListFilter.createFilterWithType( 973 ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); 974 mAllFragment.setFilter(filter); 975 } else { 976 filter = ContactListFilter.createFilterWithType( 977 ContactListFilter.FILTER_TYPE_SINGLE_CONTACT); 978 mAllFragment.setFilter(filter, false); 979 } 980 mContactListFilterController.setContactListFilter(filter, true); 981 } 982 } 983 984 private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener { 985 @Override 986 public void onContactNotFound() { 987 // Nothing needs to be done here 988 } 989 990 @Override 991 public void onDetailsLoaded(final ContactLoader.Result result) { 992 if (result == null) { 993 return; 994 } 995 // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the 996 // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler} 997 // on the main thread to execute later. 998 mHandler.post(new Runnable() { 999 @Override 1000 public void run() { 1001 if (!mContactDetailLayoutController.isInitialized()) { 1002 mContactDetailLayoutController.setContactDetailFragment( 1003 mContactDetailFragment); 1004 mContactDetailLayoutController.setContactDetailUpdatesFragment( 1005 mContactDetailUpdatesFragment); 1006 mContactDetailLayoutController.initialize(); 1007 } 1008 mContactDetailLayoutController.setContactData(result); 1009 } 1010 }); 1011 } 1012 1013 @Override 1014 public void onEditRequested(Uri contactLookupUri) { 1015 startActivityForResult( 1016 new Intent(Intent.ACTION_EDIT, contactLookupUri), SUBACTIVITY_EDIT_CONTACT); 1017 } 1018 1019 @Override 1020 public void onDeleteRequested(Uri contactUri) { 1021 ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); 1022 } 1023 } 1024 1025 public class ContactDetailFragmentListener implements ContactDetailFragment.Listener { 1026 @Override 1027 public void onItemClicked(Intent intent) { 1028 try { 1029 startActivity(intent); 1030 } catch (ActivityNotFoundException e) { 1031 Log.e(TAG, "No activity found for intent: " + intent); 1032 } 1033 } 1034 1035 @Override 1036 public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account) { 1037 Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy, 1038 Toast.LENGTH_LONG).show(); 1039 Intent serviceIntent = ContactSaveService.createNewRawContactIntent( 1040 PeopleActivity.this, values, account, 1041 PeopleActivity.class, Intent.ACTION_VIEW); 1042 startService(serviceIntent); 1043 } 1044 } 1045 1046 private class ContactsUnavailableFragmentListener 1047 implements OnContactsUnavailableActionListener { 1048 1049 @Override 1050 public void onCreateNewContactAction() { 1051 startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); 1052 } 1053 1054 @Override 1055 public void onAddAccountAction() { 1056 Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); 1057 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1058 intent.putExtra(Settings.EXTRA_AUTHORITIES, 1059 new String[] { ContactsContract.AUTHORITY }); 1060 startActivity(intent); 1061 } 1062 1063 @Override 1064 public void onImportContactsFromFileAction() { 1065 AccountSelectionUtil.doImportFromSdCard(PeopleActivity.this, null); 1066 } 1067 1068 @Override 1069 public void onFreeInternalStorageAction() { 1070 startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)); 1071 } 1072 } 1073 1074 private final class StrequentContactListFragmentListener 1075 implements ContactTileListFragment.Listener { 1076 @Override 1077 public void onContactSelected(Uri contactUri) { 1078 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 1079 setupContactDetailFragment(contactUri); 1080 } else { 1081 startActivity(new Intent(Intent.ACTION_VIEW, contactUri)); 1082 } 1083 } 1084 } 1085 1086 private final class GroupBrowserActionListener implements OnGroupBrowserActionListener { 1087 1088 @Override 1089 public void onViewGroupAction(Uri groupUri) { 1090 if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { 1091 setupGroupDetailFragment(groupUri); 1092 } else { 1093 Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class); 1094 intent.setData(groupUri); 1095 startActivity(intent); 1096 } 1097 } 1098 } 1099 1100 private class GroupDetailFragmentListener implements GroupDetailFragment.Listener { 1101 @Override 1102 public void onGroupSizeUpdated(String size) { 1103 // Nothing needs to be done here because the size will be displayed in the detail 1104 // fragment 1105 } 1106 1107 @Override 1108 public void onGroupTitleUpdated(String title) { 1109 // Nothing needs to be done here because the title will be displayed in the detail 1110 // fragment 1111 } 1112 1113 @Override 1114 public void onGroupSourceUpdated( 1115 String accountTypeString, String groupSourceAction, String groupSourceUri) { 1116 // Nothing needs to be done here because the group source will be displayed in the 1117 // detail fragment 1118 } 1119 1120 @Override 1121 public void onEditRequested(Uri groupUri) { 1122 final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); 1123 intent.setData(groupUri); 1124 intent.setAction(Intent.ACTION_EDIT); 1125 startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP); 1126 } 1127 1128 @Override 1129 public void onContactSelected(Uri contactUri) { 1130 // Nothing needs to be done here because either quickcontact will be displayed 1131 // or activity will take care of selection 1132 } 1133 } 1134 1135 public void startActivityAndForwardResult(final Intent intent) { 1136 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 1137 1138 // Forward extras to the new activity 1139 Bundle extras = getIntent().getExtras(); 1140 if (extras != null) { 1141 intent.putExtras(extras); 1142 } 1143 startActivity(intent); 1144 finish(); 1145 } 1146 1147 @Override 1148 public boolean onCreateOptionsMenu(Menu menu) { 1149// STOPSHIP Un-comment it once b/5027071 is fixed. 1150// if (!areContactsAvailable()) { 1151// If contacts aren't available, hide all menu items. 1152// return false; 1153// } 1154 super.onCreateOptionsMenu(menu); 1155 1156 MenuInflater inflater = getMenuInflater(); 1157 inflater.inflate(R.menu.actions, menu); 1158 1159 // On narrow screens we specify a NEW group button in the {@link ActionBar}, so that 1160 // it can be in the overflow menu. On wide screens, we use a custom view because we need 1161 // its location for anchoring the account-selector popup. 1162 final MenuItem addGroup = menu.findItem(R.id.menu_custom_add_group); 1163 if (addGroup != null) { 1164 mAddGroupImageView = getLayoutInflater().inflate( 1165 R.layout.add_group_menu_item, null, false); 1166 View item = mAddGroupImageView.findViewById(R.id.menu_item); 1167 item.setOnClickListener(new OnClickListener() { 1168 @Override 1169 public void onClick(View v) { 1170 createNewGroupWithAccountDisambiguation(); 1171 } 1172 }); 1173 addGroup.setActionView(mAddGroupImageView); 1174 } 1175 return true; 1176 } 1177 1178 private void invalidateOptionsMenuIfNeeded() { 1179 if (isOptionsMenuChanged()) { 1180 invalidateOptionsMenu(); 1181 } 1182 } 1183 1184 public boolean isOptionsMenuChanged() { 1185 if (mOptionsMenuContactsAvailable != areContactsAvailable()) { 1186 return true; 1187 } 1188 1189 if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) { 1190 return true; 1191 } 1192 1193 if (mContactDetailLoaderFragment != null && 1194 mContactDetailLoaderFragment.isOptionsMenuChanged()) { 1195 return true; 1196 } 1197 1198 if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) { 1199 return true; 1200 } 1201 1202 return false; 1203 } 1204 1205 @Override 1206 public boolean onPrepareOptionsMenu(Menu menu) { 1207 mOptionsMenuContactsAvailable = areContactsAvailable(); 1208 if (!mOptionsMenuContactsAvailable) { 1209 // STOPSHIP Remove makeAllMenuItemsVisible()when STOPSHIP in onCreateOptionsMenu() is 1210 // fixed. 1211 makeAllMenuItemsVisible(menu, false); 1212 return false; 1213 } 1214 makeAllMenuItemsVisible(menu, true); 1215 1216 final MenuItem searchMenu = menu.findItem(R.id.menu_search); 1217 final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact); 1218 MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group); 1219 if (addGroupMenu == null) { 1220 addGroupMenu = menu.findItem(R.id.menu_custom_add_group); 1221 } 1222 1223 if (mActionBarAdapter.isSearchMode()) { 1224 addContactMenu.setVisible(false); 1225 addGroupMenu.setVisible(false); 1226 } else { 1227 switch (mActionBarAdapter.getCurrentTab()) { 1228 case FAVORITES: 1229 // TODO: Fall through until we determine what the menu items should be for 1230 // this tab 1231 case ALL: 1232 addContactMenu.setVisible(true); 1233 addGroupMenu.setVisible(false); 1234 break; 1235 case GROUPS: 1236 addContactMenu.setVisible(false); 1237 addGroupMenu.setVisible(true); 1238 break; 1239 } 1240 } 1241 1242 if (searchMenu != null) { 1243 // Don't show the search menu in search mode. 1244 searchMenu.setVisible(!mActionBarAdapter.isSearchMode()); 1245 } 1246 1247 MenuItem settings = menu.findItem(R.id.menu_settings); 1248 if (settings != null) { 1249 settings.setVisible(!ContactsPreferenceActivity.isEmpty(this)); 1250 } 1251 1252 return true; 1253 } 1254 1255 private void makeAllMenuItemsVisible(Menu menu, boolean visible) { 1256 final int itemCount = menu.size(); 1257 for (int i = 0; i < itemCount; i++) { 1258 menu.getItem(i).setVisible(visible); 1259 } 1260 } 1261 1262 @Override 1263 public boolean onOptionsItemSelected(MenuItem item) { 1264 switch (item.getItemId()) { 1265 case android.R.id.home: { 1266 // The home icon on the action bar is pressed 1267 if (mActionBarAdapter.isUpShowing()) { 1268 // "UP" icon press -- should be treated as "back". 1269 onBackPressed(); 1270 } 1271 return true; 1272 } 1273 case R.id.menu_settings: { 1274 final Intent intent = new Intent(this, ContactsPreferenceActivity.class); 1275 // as there is only one section right now, make sure it is selected 1276 // on small screens, this also hides the section selector 1277 // Due to b/5045558, this code unfortunately only works properly on phones 1278 boolean settingsAreMultiPane = getResources().getBoolean( 1279 com.android.internal.R.bool.preferences_prefer_dual_pane); 1280 if (!settingsAreMultiPane) { 1281 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, 1282 DisplayOptionsPreferenceFragment.class.getName()); 1283 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE, 1284 R.string.preference_displayOptions); 1285 } 1286 startActivity(intent); 1287 return true; 1288 } 1289 case R.id.menu_contacts_filter: { 1290 final Intent intent = new Intent(this, AccountFilterActivity.class); 1291 startActivityForResult(intent, SUBACTIVITY_ACCOUNT_FILTER); 1292 return true; 1293 } 1294 case R.id.menu_search: { 1295 onSearchRequested(); 1296 return true; 1297 } 1298 case R.id.menu_add_contact: { 1299 final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); 1300 startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT); 1301 return true; 1302 } 1303 case R.id.menu_add_group: { 1304 createNewGroupWithAccountDisambiguation(); 1305 return true; 1306 } 1307 case R.id.menu_import_export: { 1308 ImportExportDialogFragment.show(getFragmentManager()); 1309 return true; 1310 } 1311 case R.id.menu_accounts: { 1312 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); 1313 intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] { 1314 ContactsContract.AUTHORITY 1315 }); 1316 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1317 startActivity(intent); 1318 return true; 1319 } 1320 } 1321 return false; 1322 } 1323 1324 private void createNewGroupWithAccountDisambiguation() { 1325 final ArrayList<Account> accounts = 1326 AccountTypeManager.getInstance(this).getAccounts(true); 1327 if (accounts.size() <= 1 || mAddGroupImageView == null) { 1328 // No account to choose or no control to anchor the popup-menu to 1329 // ==> just go straight to the editor which will disambig if necessary 1330 final Intent intent = new Intent(this, GroupEditorActivity.class); 1331 intent.setAction(Intent.ACTION_INSERT); 1332 startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); 1333 return; 1334 } 1335 1336 final ListPopupWindow popup = new ListPopupWindow(this, null); 1337 popup.setWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width)); 1338 popup.setAnchorView(mAddGroupImageView); 1339 // Create a list adapter with all writeable accounts (assume that the writeable accounts all 1340 // allow group creation). 1341 final AccountsListAdapter adapter = new AccountsListAdapter(this, true); 1342 popup.setAdapter(adapter); 1343 popup.setOnItemClickListener(new OnItemClickListener() { 1344 @Override 1345 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1346 popup.dismiss(); 1347 final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); 1348 intent.setAction(Intent.ACTION_INSERT); 1349 intent.putExtra(Intents.Insert.ACCOUNT, adapter.getItem(position)); 1350 startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); 1351 } 1352 }); 1353 popup.setModal(true); 1354 popup.show(); 1355 } 1356 1357 @Override 1358 public boolean onSearchRequested() { // Search key pressed. 1359 mActionBarAdapter.setSearchMode(true); 1360 return true; 1361 } 1362 1363 @Override 1364 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1365 switch (requestCode) { 1366 case SUBACTIVITY_ACCOUNT_FILTER: { 1367 if (resultCode == Activity.RESULT_OK) { 1368 ContactListFilter filter = (ContactListFilter) data.getParcelableExtra( 1369 AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER); 1370 if (filter == null) { 1371 return; 1372 } 1373 // If this is a custom filter, launch the activity to customize the display list 1374 if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) { 1375 final Intent intent = new Intent(this, 1376 CustomContactListFilterActivity.class); 1377 startActivityForResult(intent, SUBACTIVITY_CUSTOMIZE_FILTER); 1378 } else { 1379 mContactListFilterController.setContactListFilter(filter, true); 1380 } 1381 } 1382 break; 1383 } 1384 case SUBACTIVITY_CUSTOMIZE_FILTER: { 1385 if (resultCode == Activity.RESULT_OK) { 1386 mContactListFilterController.selectCustomFilter(); 1387 } 1388 break; 1389 } 1390 case SUBACTIVITY_EDIT_CONTACT: 1391 case SUBACTIVITY_NEW_CONTACT: { 1392 if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) { 1393 mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT); 1394 mAllFragment.reloadDataAndSetSelectedUri(data.getData()); 1395 } 1396 break; 1397 } 1398 1399 case SUBACTIVITY_NEW_GROUP: 1400 case SUBACTIVITY_EDIT_GROUP: { 1401 if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) { 1402 mRequest.setActionCode(ContactsRequest.ACTION_GROUP); 1403 mGroupsFragment.setSelectedUri(data.getData()); 1404 } 1405 break; 1406 } 1407 1408 // TODO: Using the new startActivityWithResultFromFragment API this should not be needed 1409 // anymore 1410 case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER: 1411 if (resultCode == RESULT_OK) { 1412 mAllFragment.onPickerResult(data); 1413 } 1414 1415// TODO fix or remove multipicker code 1416// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) { 1417// // Finish the activity if the sub activity was canceled as back key is used 1418// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES. 1419// finish(); 1420// } 1421// break; 1422 } 1423 } 1424 1425 @Override 1426 public boolean onKeyDown(int keyCode, KeyEvent event) { 1427 // TODO move to the fragment 1428 switch (keyCode) { 1429// case KeyEvent.KEYCODE_CALL: { 1430// if (callSelection()) { 1431// return true; 1432// } 1433// break; 1434// } 1435 1436 case KeyEvent.KEYCODE_DEL: { 1437 if (deleteSelection()) { 1438 return true; 1439 } 1440 break; 1441 } 1442 default: { 1443 // Bring up the search UI if the user starts typing 1444 final int unicodeChar = event.getUnicodeChar(); 1445 if (unicodeChar != 0 && !Character.isWhitespace(unicodeChar)) { 1446 String query = new String(new int[]{ unicodeChar }, 0, 1); 1447 if (!mActionBarAdapter.isSearchMode()) { 1448 mActionBarAdapter.setQueryString(query); 1449 mActionBarAdapter.setSearchMode(true); 1450 return true; 1451 } 1452 } 1453 } 1454 } 1455 1456 return super.onKeyDown(keyCode, event); 1457 } 1458 1459 @Override 1460 public void onBackPressed() { 1461 if (mActionBarAdapter.isSearchMode()) { 1462 mActionBarAdapter.setSearchMode(false); 1463 } else { 1464 super.onBackPressed(); 1465 } 1466 } 1467 1468 private boolean deleteSelection() { 1469 // TODO move to the fragment 1470// if (mActionCode == ContactsRequest.ACTION_DEFAULT) { 1471// final int position = mListView.getSelectedItemPosition(); 1472// if (position != ListView.INVALID_POSITION) { 1473// Uri contactUri = getContactUri(position); 1474// if (contactUri != null) { 1475// doContactDelete(contactUri); 1476// return true; 1477// } 1478// } 1479// } 1480 return false; 1481 } 1482 1483 @Override 1484 protected void onSaveInstanceState(Bundle outState) { 1485 super.onSaveInstanceState(outState); 1486 mActionBarAdapter.onSaveInstanceState(outState); 1487 if (mContactDetailLayoutController != null) { 1488 mContactDetailLayoutController.onSaveInstanceState(outState); 1489 } 1490 1491 // Clear the listener to make sure we don't get callbacks after onSaveInstanceState, 1492 // in order to avoid doing fragment transactions after it. 1493 // TODO Figure out a better way to deal with the issue. 1494 mActionBarAdapter.setListener(null); 1495 } 1496 1497 @Override 1498 protected void onRestoreInstanceState(Bundle inState) { 1499 super.onRestoreInstanceState(inState); 1500 if (mContactDetailLayoutController != null) { 1501 mContactDetailLayoutController.onRestoreInstanceState(inState); 1502 } 1503 } 1504 1505 @Override 1506 public DialogManager getDialogManager() { 1507 return mDialogManager; 1508 } 1509 1510 // Visible for testing 1511 public ContactBrowseListFragment getListFragment() { 1512 return mAllFragment; 1513 } 1514 1515 // Visible for testing 1516 public ContactDetailFragment getDetailFragment() { 1517 return mContactDetailFragment; 1518 } 1519} 1520