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