/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.contacts.activities; import com.android.contacts.ContactLoader; import com.android.contacts.ContactSaveService; import com.android.contacts.ContactsActivity; import com.android.contacts.ContactsUtils; import com.android.contacts.R; import com.android.contacts.activities.ActionBarAdapter.TabState; import com.android.contacts.detail.ContactDetailFragment; import com.android.contacts.detail.ContactDetailLayoutController; import com.android.contacts.detail.ContactDetailUpdatesFragment; import com.android.contacts.detail.ContactLoaderFragment; import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener; import com.android.contacts.group.GroupBrowseListFragment; import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener; import com.android.contacts.group.GroupDetailFragment; import com.android.contacts.interactions.ContactDeletionInteraction; import com.android.contacts.interactions.ImportExportDialogFragment; import com.android.contacts.interactions.PhoneNumberInteraction; import com.android.contacts.list.ContactBrowseListFragment; import com.android.contacts.list.ContactEntryListFragment; import com.android.contacts.list.ContactListFilter; import com.android.contacts.list.ContactListFilterController; import com.android.contacts.list.ContactTileAdapter.DisplayType; import com.android.contacts.list.ContactTileFrequentFragment; import com.android.contacts.list.ContactTileListFragment; import com.android.contacts.list.ContactsIntentResolver; import com.android.contacts.list.ContactsRequest; import com.android.contacts.list.ContactsUnavailableFragment; import com.android.contacts.list.DefaultContactBrowseListFragment; import com.android.contacts.list.DirectoryListLoader; import com.android.contacts.list.OnContactBrowserActionListener; import com.android.contacts.list.OnContactsUnavailableActionListener; import com.android.contacts.list.ProviderStatusLoader; import com.android.contacts.list.ProviderStatusLoader.ProviderStatusListener; import com.android.contacts.model.AccountTypeManager; import com.android.contacts.model.AccountWithDataSet; import com.android.contacts.preference.ContactsPreferenceActivity; import com.android.contacts.preference.DisplayOptionsPreferenceFragment; import com.android.contacts.util.AccountFilterUtil; import com.android.contacts.util.AccountPromptUtils; import com.android.contacts.util.AccountSelectionUtil; import com.android.contacts.util.AccountsListAdapter; import com.android.contacts.util.AccountsListAdapter.AccountListFilter; import com.android.contacts.util.Constants; import com.android.contacts.util.DialogManager; import com.android.contacts.util.PhoneCapabilityTester; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Intent; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.preference.PreferenceActivity; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Intents; import android.provider.ContactsContract.ProviderStatus; import android.provider.ContactsContract.QuickContact; import android.provider.Settings; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListPopupWindow; import android.widget.Toast; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * Displays a list to browse contacts. For xlarge screens, this also displays a detail-pane on * the right. */ public class PeopleActivity extends ContactsActivity implements View.OnCreateContextMenuListener, ActionBarAdapter.Listener, DialogManager.DialogShowingViewActivity, ContactListFilterController.ContactListFilterListener, ProviderStatusListener { private static final String TAG = "PeopleActivity"; // These values needs to start at 2. See {@link ContactEntryListFragment}. private static final int SUBACTIVITY_NEW_CONTACT = 2; private static final int SUBACTIVITY_EDIT_CONTACT = 3; private static final int SUBACTIVITY_NEW_GROUP = 4; private static final int SUBACTIVITY_EDIT_GROUP = 5; private static final int SUBACTIVITY_ACCOUNT_FILTER = 6; private final DialogManager mDialogManager = new DialogManager(this); private ContactsIntentResolver mIntentResolver; private ContactsRequest mRequest; private ActionBarAdapter mActionBarAdapter; private ContactDetailFragment mContactDetailFragment; private ContactDetailUpdatesFragment mContactDetailUpdatesFragment; private ContactLoaderFragment mContactDetailLoaderFragment; private final ContactDetailLoaderFragmentListener mContactDetailLoaderFragmentListener = new ContactDetailLoaderFragmentListener(); private GroupDetailFragment mGroupDetailFragment; private final GroupDetailFragmentListener mGroupDetailFragmentListener = new GroupDetailFragmentListener(); private ContactTileListFragment.Listener mFavoritesFragmentListener = new StrequentContactListFragmentListener(); private ContactListFilterController mContactListFilterController; private ContactsUnavailableFragment mContactsUnavailableFragment; private ProviderStatusLoader mProviderStatusLoader; private int mProviderStatus = -1; private boolean mOptionsMenuContactsAvailable; /** * Showing a list of Contacts. Also used for showing search results in search mode. */ private DefaultContactBrowseListFragment mAllFragment; private ContactTileListFragment mFavoritesFragment; private ContactTileFrequentFragment mFrequentFragment; private GroupBrowseListFragment mGroupsFragment; private View mFavoritesView; private View mBrowserView; private View mDetailsView; private View mAddGroupImageView; /** ViewPager for swipe, used only on the phone (i.e. one-pane mode) */ private ViewPager mTabPager; private TabPagerAdapter mTabPagerAdapter; private final TabPagerListener mTabPagerListener = new TabPagerListener(); private ContactDetailLayoutController mContactDetailLayoutController; private final Handler mHandler = new Handler(); /** * True if this activity instance is a re-created one. i.e. set true after orientation change. * This is set in {@link #onCreate} for later use in {@link #onStart}. */ private boolean mIsRecreatedInstance; /** * If {@link #configureFragments(boolean)} is already called. Used to avoid calling it twice * in {@link #onStart}. * (This initialization only needs to be done once in onStart() when the Activity was just * created from scratch -- i.e. onCreate() was just called) */ private boolean mFragmentInitialized; /** * Whether or not the current contact filter is valid or not. We need to do a check on * start of the app to verify that the user is not in single contact mode. If so, we should * dynamically change the filter, unless the incoming intent specifically requested a contact * that should be displayed in that mode. */ private boolean mCurrentFilterIsValid; /** Sequential ID assigned to each instance; used for logging */ private final int mInstanceId; private static final AtomicInteger sNextInstanceId = new AtomicInteger(); public PeopleActivity() { mInstanceId = sNextInstanceId.getAndIncrement(); mIntentResolver = new ContactsIntentResolver(this); mProviderStatusLoader = new ProviderStatusLoader(this); } @Override public String toString() { // Shown on logcat return String.format("%s@%d", getClass().getSimpleName(), mInstanceId); } public boolean areContactsAvailable() { return mProviderStatus == ProviderStatus.STATUS_NORMAL; } private boolean areContactWritableAccountsAvailable() { return ContactsUtils.areContactWritableAccountsAvailable(this); } private boolean areGroupWritableAccountsAvailable() { return ContactsUtils.areGroupWritableAccountsAvailable(this); } /** * Initialize fragments that are (or may not be) in the layout. * * For the fragments that are in the layout, we initialize them in * {@link #createViewsAndFragments(Bundle)} after inflating the layout. * * However, there are special fragments which may not be in the layout, so we have to do the * initialization here. * The target fragments are: * - {@link ContactDetailFragment} and {@link ContactDetailUpdatesFragment}: They may not be * in the layout depending on the configuration. (i.e. portrait) * - {@link ContactsUnavailableFragment}: We always create it at runtime. */ @Override public void onAttachFragment(Fragment fragment) { if (fragment instanceof ContactDetailFragment) { mContactDetailFragment = (ContactDetailFragment) fragment; } else if (fragment instanceof ContactDetailUpdatesFragment) { mContactDetailUpdatesFragment = (ContactDetailUpdatesFragment) fragment; } else if (fragment instanceof ContactsUnavailableFragment) { mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment; mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader); mContactsUnavailableFragment.setOnContactsUnavailableActionListener( new ContactsUnavailableFragmentListener()); } } @Override protected void onCreate(Bundle savedState) { if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) { Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start"); } super.onCreate(savedState); if (!processIntent(false)) { finish(); return; } mContactListFilterController = ContactListFilterController.getInstance(this); mContactListFilterController.checkFilterValidity(false); mContactListFilterController.addListener(this); mIsRecreatedInstance = (savedState != null); createViewsAndFragments(savedState); if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) { Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish"); } } @Override protected void onNewIntent(Intent intent) { setIntent(intent); if (!processIntent(true)) { finish(); return; } mActionBarAdapter.initialize(null, mRequest); mContactListFilterController.checkFilterValidity(false); mCurrentFilterIsValid = true; // Re-configure fragments. configureFragments(true /* from request */); invalidateOptionsMenuIfNeeded(); } /** * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect * is needed. * * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}. * @return {@code true} if {@link PeopleActivity} should continue running. {@code false} * if it shouldn't, in which case the caller should finish() itself and shouldn't do * farther initialization. */ private boolean processIntent(boolean forNewIntent) { // Extract relevant information from the intent mRequest = mIntentResolver.resolveIntent(getIntent()); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent + " intent=" + getIntent() + " request=" + mRequest); } if (!mRequest.isValid()) { setResult(RESULT_CANCELED); return false; } Intent redirect = mRequest.getRedirectIntent(); if (redirect != null) { // Need to start a different activity startActivity(redirect); return false; } if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT && !PhoneCapabilityTester.isUsingTwoPanes(this)) { redirect = new Intent(this, ContactDetailActivity.class); redirect.setAction(Intent.ACTION_VIEW); redirect.setData(mRequest.getContactUri()); startActivity(redirect); return false; } setTitle(mRequest.getActivityTitle()); return true; } private void createViewsAndFragments(Bundle savedState) { setContentView(R.layout.people_activity); final FragmentManager fragmentManager = getFragmentManager(); // Hide all tabs (the current tab will later be reshown once a tab is selected) final FragmentTransaction transaction = fragmentManager.beginTransaction(); // Prepare the fragments which are used both on 1-pane and on 2-pane. boolean isUsingTwoPanes = PhoneCapabilityTester.isUsingTwoPanes(this); if (isUsingTwoPanes) { mFavoritesFragment = getFragment(R.id.favorites_fragment); mAllFragment = getFragment(R.id.all_fragment); mGroupsFragment = getFragment(R.id.groups_fragment); } else { mTabPager = getView(R.id.tab_pager); mTabPagerAdapter = new TabPagerAdapter(); mTabPager.setAdapter(mTabPagerAdapter); mTabPager.setOnPageChangeListener(mTabPagerListener); final String FAVORITE_TAG = "tab-pager-favorite"; final String ALL_TAG = "tab-pager-all"; final String GROUPS_TAG = "tab-pager-groups"; // Create the fragments and add as children of the view pager. // The pager adapter will only change the visibility; it'll never create/destroy // fragments. // However, if it's after screen rotation, the fragments have been re-created by // the fragment manager, so first see if there're already the target fragments // existing. mFavoritesFragment = (ContactTileListFragment) fragmentManager.findFragmentByTag(FAVORITE_TAG); mAllFragment = (DefaultContactBrowseListFragment) fragmentManager.findFragmentByTag(ALL_TAG); mGroupsFragment = (GroupBrowseListFragment) fragmentManager.findFragmentByTag(GROUPS_TAG); if (mFavoritesFragment == null) { mFavoritesFragment = new ContactTileListFragment(); mAllFragment = new DefaultContactBrowseListFragment(); mGroupsFragment = new GroupBrowseListFragment(); transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG); transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG); transaction.add(R.id.tab_pager, mGroupsFragment, GROUPS_TAG); } } mFavoritesFragment.setListener(mFavoritesFragmentListener); mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener()); mGroupsFragment.setListener(new GroupBrowserActionListener()); // Hide all fragments for now. We adjust visibility when we get onSelectedTabChanged() // from ActionBarAdapter. transaction.hide(mFavoritesFragment); transaction.hide(mAllFragment); transaction.hide(mGroupsFragment); if (isUsingTwoPanes) { // Prepare 2-pane only fragments/views... // Container views for fragments mFavoritesView = getView(R.id.favorites_view); mDetailsView = getView(R.id.details_view); mBrowserView = getView(R.id.browse_view); // 2-pane only fragments mFrequentFragment = getFragment(R.id.frequent_fragment); mFrequentFragment.setListener(mFavoritesFragmentListener); mFrequentFragment.setDisplayType(DisplayType.FREQUENT_ONLY); mFrequentFragment.enableQuickContact(true); mContactDetailLoaderFragment = getFragment(R.id.contact_detail_loader_fragment); mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener); mGroupDetailFragment = getFragment(R.id.group_detail_fragment); mGroupDetailFragment.setListener(mGroupDetailFragmentListener); mGroupDetailFragment.setQuickContact(true); if (mContactDetailFragment != null) { transaction.hide(mContactDetailFragment); } transaction.hide(mGroupDetailFragment); // Configure contact details mContactDetailLayoutController = new ContactDetailLayoutController(this, savedState, getFragmentManager(), findViewById(R.id.contact_detail_container), new ContactDetailFragmentListener()); } transaction.commitAllowingStateLoss(); fragmentManager.executePendingTransactions(); // Setting Properties after fragment is created if (PhoneCapabilityTester.isUsingTwoPanes(this)) { mFavoritesFragment.enableQuickContact(true); mFavoritesFragment.setDisplayType(DisplayType.STARRED_ONLY); } else { mFavoritesFragment.setDisplayType(DisplayType.STREQUENT); } // Configure action bar mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar(), isUsingTwoPanes); mActionBarAdapter.initialize(savedState, mRequest); invalidateOptionsMenuIfNeeded(); } @Override protected void onStart() { if (!mFragmentInitialized) { mFragmentInitialized = true; /* Configure fragments if we haven't. * * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}. * * However, because this method may indirectly touch views in fragments but fragments * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT * have views until {@link Activity#onCreate} finishes (they would if they were inflated * from a layout), we need to do it here in {@link #onStart()}. * * (When {@link Fragment#onCreateView} is called is different in the former case and * in the latter case, unfortunately.) * * Also, we skip most of the work in it if the activity is a re-created one. * (so the argument.) */ configureFragments(!mIsRecreatedInstance); } else if (PhoneCapabilityTester.isUsingTwoPanes(this) && !mCurrentFilterIsValid) { // We only want to do the filter check in onStart for wide screen devices where it // is often possible to get into single contact mode. Only do this check if // the filter hasn't already been set properly (i.e. onCreate or onActivityResult). // Since there is only one {@link ContactListFilterController} across multiple // activity instances, make sure the filter controller is in sync withthe current // contact list fragment filter. // TODO: Clean this up. Perhaps change {@link ContactListFilterController} to not be a // singleton? mContactListFilterController.setContactListFilter(mAllFragment.getFilter(), true); mContactListFilterController.checkFilterValidity(true); mCurrentFilterIsValid = true; } super.onStart(); } @Override protected void onPause() { mOptionsMenuContactsAvailable = false; mProviderStatus = -1; mProviderStatusLoader.setProviderStatusListener(null); super.onPause(); } @Override protected void onResume() { super.onResume(); mProviderStatusLoader.setProviderStatusListener(this); showContactsUnavailableFragmentIfNecessary(); // Re-register the listener, which may have been cleared when onSaveInstanceState was // called. See also: onSaveInstanceState mActionBarAdapter.setListener(this); if (mTabPager != null) { mTabPager.setOnPageChangeListener(mTabPagerListener); } // Current tab may have changed since the last onSaveInstanceState(). Make sure // the actual contents match the tab. updateFragmentsVisibility(); } @Override protected void onStop() { super.onStop(); mCurrentFilterIsValid = false; } @Override protected void onDestroy() { // Some of variables will be null if this Activity redirects Intent. // See also onCreate() or other methods called during the Activity's initialization. if (mActionBarAdapter != null) { mActionBarAdapter.setListener(null); } if (mContactListFilterController != null) { mContactListFilterController.removeListener(this); } super.onDestroy(); } private void configureFragments(boolean fromRequest) { if (fromRequest) { ContactListFilter filter = null; int actionCode = mRequest.getActionCode(); boolean searchMode = mRequest.isSearchMode(); TabState tabToOpen = null; switch (actionCode) { case ContactsRequest.ACTION_ALL_CONTACTS: filter = ContactListFilter.createFilterWithType( ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); tabToOpen = TabState.ALL; break; case ContactsRequest.ACTION_CONTACTS_WITH_PHONES: filter = ContactListFilter.createFilterWithType( ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY); tabToOpen = TabState.ALL; break; case ContactsRequest.ACTION_FREQUENT: case ContactsRequest.ACTION_STREQUENT: case ContactsRequest.ACTION_STARRED: tabToOpen = TabState.FAVORITES; break; case ContactsRequest.ACTION_VIEW_CONTACT: // We redirect this intent to the detail activity on 1-pane, so we don't get // here. It's only for 2-pane. tabToOpen = TabState.ALL; break; case ContactsRequest.ACTION_GROUP: tabToOpen = TabState.GROUPS; break; } if (tabToOpen != null) { mActionBarAdapter.setCurrentTab(tabToOpen); } if (filter != null) { mContactListFilterController.setContactListFilter(filter, false); searchMode = false; } if (mRequest.getContactUri() != null) { searchMode = false; } mActionBarAdapter.setSearchMode(searchMode); configureContactListFragmentForRequest(); } configureContactListFragment(); configureGroupListFragment(); invalidateOptionsMenuIfNeeded(); } @Override public void onContactListFilterChanged() { if (mAllFragment == null || !mAllFragment.isAdded()) { return; } mAllFragment.setFilter(mContactListFilterController.getFilter()); invalidateOptionsMenuIfNeeded(); } private void setupContactDetailFragment(final Uri contactLookupUri) { mContactDetailLoaderFragment.loadUri(contactLookupUri); invalidateOptionsMenuIfNeeded(); } private void setupGroupDetailFragment(Uri groupUri) { mGroupDetailFragment.loadGroup(groupUri); invalidateOptionsMenuIfNeeded(); } /** * Handler for action bar actions. */ @Override public void onAction(Action action) { switch (action) { case START_SEARCH_MODE: // Tell the fragments that we're in the search mode configureFragments(false /* from request */); updateFragmentsVisibility(); invalidateOptionsMenu(); break; case STOP_SEARCH_MODE: setQueryTextToFragment(""); updateFragmentsVisibility(); invalidateOptionsMenu(); break; case CHANGE_SEARCH_QUERY: setQueryTextToFragment(mActionBarAdapter.getQueryString()); break; default: throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action); } } @Override public void onSelectedTabChanged() { updateFragmentsVisibility(); } /** * Updates the fragment/view visibility according to the current mode, such as * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}. */ private void updateFragmentsVisibility() { TabState tab = mActionBarAdapter.getCurrentTab(); // We use ViewPager on 1-pane. if (!PhoneCapabilityTester.isUsingTwoPanes(this)) { if (mActionBarAdapter.isSearchMode()) { mTabPagerAdapter.setSearchMode(true); } else { // No smooth scrolling if quitting from the search mode. final boolean wasSearchMode = mTabPagerAdapter.isSearchMode(); mTabPagerAdapter.setSearchMode(false); int tabIndex = tab.ordinal(); if (mTabPager.getCurrentItem() != tabIndex) { mTabPager.setCurrentItem(tabIndex, !wasSearchMode); } } invalidateOptionsMenu(); showEmptyStateForTab(tab); if (tab == TabState.GROUPS) { mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); } return; } // for the tablet... // If in search mode, we use the all list + contact details to show the result. if (mActionBarAdapter.isSearchMode()) { tab = TabState.ALL; } switch (tab) { case FAVORITES: mFavoritesView.setVisibility(View.VISIBLE); mBrowserView.setVisibility(View.GONE); mDetailsView.setVisibility(View.GONE); break; case GROUPS: mFavoritesView.setVisibility(View.GONE); mBrowserView.setVisibility(View.VISIBLE); mDetailsView.setVisibility(View.VISIBLE); mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); break; case ALL: mFavoritesView.setVisibility(View.GONE); mBrowserView.setVisibility(View.VISIBLE); mDetailsView.setVisibility(View.VISIBLE); break; } FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction ft = fragmentManager.beginTransaction(); // Note mContactDetailLoaderFragment is an invisible fragment, but we still have to show/ // hide it so its options menu will be shown/hidden. switch (tab) { case FAVORITES: showFragment(ft, mFavoritesFragment); showFragment(ft, mFrequentFragment); hideFragment(ft, mAllFragment); hideFragment(ft, mContactDetailLoaderFragment); hideFragment(ft, mContactDetailFragment); hideFragment(ft, mGroupsFragment); hideFragment(ft, mGroupDetailFragment); break; case ALL: hideFragment(ft, mFavoritesFragment); hideFragment(ft, mFrequentFragment); showFragment(ft, mAllFragment); showFragment(ft, mContactDetailLoaderFragment); showFragment(ft, mContactDetailFragment); hideFragment(ft, mGroupsFragment); hideFragment(ft, mGroupDetailFragment); break; case GROUPS: hideFragment(ft, mFavoritesFragment); hideFragment(ft, mFrequentFragment); hideFragment(ft, mAllFragment); hideFragment(ft, mContactDetailLoaderFragment); hideFragment(ft, mContactDetailFragment); showFragment(ft, mGroupsFragment); showFragment(ft, mGroupDetailFragment); break; } if (!ft.isEmpty()) { ft.commitAllowingStateLoss(); fragmentManager.executePendingTransactions(); // When switching tabs, we need to invalidate options menu, but executing a // fragment transaction does it implicitly. We don't have to call invalidateOptionsMenu // manually. } showEmptyStateForTab(tab); } private void showEmptyStateForTab(TabState tab) { if (mContactsUnavailableFragment != null) { switch (tab) { case FAVORITES: mContactsUnavailableFragment.setMessageText( R.string.listTotalAllContactsZeroStarred, -1); break; case GROUPS: mContactsUnavailableFragment.setMessageText(R.string.noGroups, areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts); break; case ALL: mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1); break; } } } private class TabPagerListener implements ViewPager.OnPageChangeListener { @Override public void onPageScrollStateChanged(int state) { } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { // Make sure not in the search mode, in which case position != TabState.ordinal(). if (!mTabPagerAdapter.isSearchMode()) { TabState selectedTab = TabState.fromInt(position); mActionBarAdapter.setCurrentTab(selectedTab, false); showEmptyStateForTab(selectedTab); if (selectedTab == TabState.GROUPS) { mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); } invalidateOptionsMenu(); } } } /** * Adapter for the {@link ViewPager}. Unlike {@link FragmentPagerAdapter}, * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/ * {@link #destroyItem} show/hide fragments instead of attaching/detaching. * * In search mode, we always show the "all" fragment, and disable the swipe. We change the * number of items to 1 to disable the swipe. * * TODO figure out a more straight way to disable swipe. */ private class TabPagerAdapter extends PagerAdapter { private final FragmentManager mFragmentManager; private FragmentTransaction mCurTransaction = null; private boolean mTabPagerAdapterSearchMode; private Fragment mCurrentPrimaryItem; public TabPagerAdapter() { mFragmentManager = getFragmentManager(); } public boolean isSearchMode() { return mTabPagerAdapterSearchMode; } public void setSearchMode(boolean searchMode) { if (searchMode == mTabPagerAdapterSearchMode) { return; } mTabPagerAdapterSearchMode = searchMode; notifyDataSetChanged(); } @Override public int getCount() { return mTabPagerAdapterSearchMode ? 1 : TabState.values().length; } /** Gets called when the number of items changes. */ @Override public int getItemPosition(Object object) { if (mTabPagerAdapterSearchMode) { if (object == mAllFragment) { return 0; // Only 1 page in search mode } } else { if (object == mFavoritesFragment) { return TabState.FAVORITES.ordinal(); } if (object == mAllFragment) { return TabState.ALL.ordinal(); } if (object == mGroupsFragment) { return TabState.GROUPS.ordinal(); } } return POSITION_NONE; } @Override public void startUpdate(View container) { } private Fragment getFragment(int position) { if (mTabPagerAdapterSearchMode) { if (position == 0) { return mAllFragment; } } else { if (position == TabState.FAVORITES.ordinal()) { return mFavoritesFragment; } else if (position == TabState.ALL.ordinal()) { return mAllFragment; } else if (position == TabState.GROUPS.ordinal()) { return mGroupsFragment; } } throw new IllegalArgumentException("position: " + position); } @Override public Object instantiateItem(View container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } Fragment f = getFragment(position); mCurTransaction.show(f); // Non primary pages are not visible. f.setUserVisibleHint(f == mCurrentPrimaryItem); return f; } @Override public void destroyItem(View container, int position, Object object) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } mCurTransaction.hide((Fragment) object); } @Override public void finishUpdate(View container) { if (mCurTransaction != null) { mCurTransaction.commitAllowingStateLoss(); mCurTransaction = null; mFragmentManager.executePendingTransactions(); } } @Override public boolean isViewFromObject(View view, Object object) { return ((Fragment) object).getView() == view; } @Override public void setPrimaryItem(View container, int position, Object object) { Fragment fragment = (Fragment) object; if (mCurrentPrimaryItem != fragment) { if (mCurrentPrimaryItem != null) { mCurrentPrimaryItem.setUserVisibleHint(false); } if (fragment != null) { fragment.setUserVisibleHint(true); } mCurrentPrimaryItem = fragment; } } @Override public Parcelable saveState() { return null; } @Override public void restoreState(Parcelable state, ClassLoader loader) { } } private void setQueryTextToFragment(String query) { mAllFragment.setQueryString(query, true); mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode()); } private void configureContactListFragmentForRequest() { Uri contactUri = mRequest.getContactUri(); if (contactUri != null) { // For an incoming request, explicitly require a selection if we are on 2-pane UI, // (i.e. even if we view the same selected contact, the contact may no longer be // in the list, so we must refresh the list). if (PhoneCapabilityTester.isUsingTwoPanes(this)) { mAllFragment.setSelectionRequired(true); } mAllFragment.setSelectedContactUri(contactUri); } mAllFragment.setFilter(mContactListFilterController.getFilter()); setQueryTextToFragment(mActionBarAdapter.getQueryString()); if (mRequest.isDirectorySearchEnabled()) { mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT); } else { mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); } } private void configureContactListFragment() { // Filter may be changed when this Activity is in background. mAllFragment.setFilter(mContactListFilterController.getFilter()); final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); mAllFragment.setVerticalScrollbarPosition( useTwoPane ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT); mAllFragment.setSelectionVisible(useTwoPane); mAllFragment.setQuickContactEnabled(!useTwoPane); } private void configureGroupListFragment() { final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this); mGroupsFragment.setVerticalScrollbarPosition( useTwoPane ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT); mGroupsFragment.setSelectionVisible(useTwoPane); } @Override public void onProviderStatusChange() { showContactsUnavailableFragmentIfNecessary(); } private void showContactsUnavailableFragmentIfNecessary() { int providerStatus = mProviderStatusLoader.getProviderStatus(); if (providerStatus == mProviderStatus) { return; } mProviderStatus = providerStatus; View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view); View mainView = findViewById(R.id.main_view); if (mProviderStatus == ProviderStatus.STATUS_NORMAL) { contactsUnavailableView.setVisibility(View.GONE); if (mainView != null) { mainView.setVisibility(View.VISIBLE); } if (mAllFragment != null) { mAllFragment.setEnabled(true); } } else { // If there are no accounts on the device and we should show the "no account" prompt // (based on {@link SharedPreferences}), then launch the account setup activity so the // user can sign-in or create an account. if (!areContactWritableAccountsAvailable() && AccountPromptUtils.shouldShowAccountPrompt(this)) { AccountPromptUtils.launchAccountPrompt(this); return; } // Otherwise, continue setting up the page so that the user can still use the app // without an account. if (mAllFragment != null) { mAllFragment.setEnabled(false); } if (mContactsUnavailableFragment == null) { mContactsUnavailableFragment = new ContactsUnavailableFragment(); mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader); mContactsUnavailableFragment.setOnContactsUnavailableActionListener( new ContactsUnavailableFragmentListener()); getFragmentManager().beginTransaction() .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment) .commitAllowingStateLoss(); } else { mContactsUnavailableFragment.update(); } contactsUnavailableView.setVisibility(View.VISIBLE); if (mainView != null) { mainView.setVisibility(View.INVISIBLE); } TabState tab = mActionBarAdapter.getCurrentTab(); showEmptyStateForTab(tab); } invalidateOptionsMenuIfNeeded(); } private final class ContactBrowserActionListener implements OnContactBrowserActionListener { @Override public void onSelectionChange() { if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { setupContactDetailFragment(mAllFragment.getSelectedContactUri()); } } @Override public void onViewContactAction(Uri contactLookupUri) { if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { setupContactDetailFragment(contactLookupUri); } else { Intent intent = new Intent(Intent.ACTION_VIEW, contactLookupUri); // In search mode, the "up" affordance in the contact detail page should return the // user to the search results, so finish the activity when that button is selected. if (mActionBarAdapter.isSearchMode()) { intent.putExtra( ContactDetailActivity.INTENT_KEY_FINISH_ACTIVITY_ON_UP_SELECTED, true); } startActivity(intent); } } @Override public void onCreateNewContactAction() { Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); Bundle extras = getIntent().getExtras(); if (extras != null) { intent.putExtras(extras); } startActivity(intent); } @Override public void onEditContactAction(Uri contactLookupUri) { Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri); Bundle extras = getIntent().getExtras(); if (extras != null) { intent.putExtras(extras); } intent.putExtra( ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true); startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT); } @Override public void onAddToFavoritesAction(Uri contactUri) { ContentValues values = new ContentValues(1); values.put(Contacts.STARRED, 1); getContentResolver().update(contactUri, values, null, null); } @Override public void onRemoveFromFavoritesAction(Uri contactUri) { ContentValues values = new ContentValues(1); values.put(Contacts.STARRED, 0); getContentResolver().update(contactUri, values, null, null); } @Override public void onCallContactAction(Uri contactUri) { PhoneNumberInteraction.startInteractionForPhoneCall(PeopleActivity.this, contactUri); } @Override public void onSmsContactAction(Uri contactUri) { PhoneNumberInteraction.startInteractionForTextMessage(PeopleActivity.this, contactUri); } @Override public void onDeleteContactAction(Uri contactUri) { ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); } @Override public void onFinishAction() { onBackPressed(); } @Override public void onInvalidSelection() { ContactListFilter filter; ContactListFilter currentFilter = mAllFragment.getFilter(); if (currentFilter != null && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { filter = ContactListFilter.createFilterWithType( ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS); mAllFragment.setFilter(filter); } else { filter = ContactListFilter.createFilterWithType( ContactListFilter.FILTER_TYPE_SINGLE_CONTACT); mAllFragment.setFilter(filter, false); } mContactListFilterController.setContactListFilter(filter, true); } } private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener { @Override public void onContactNotFound() { // Nothing needs to be done here } @Override public void onDetailsLoaded(final ContactLoader.Result result) { if (result == null) { // Nothing is loaded. Show empty state. mContactDetailLayoutController.showEmptyState(); return; } // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler} // on the main thread to execute later. mHandler.post(new Runnable() { @Override public void run() { // If the activity is destroyed (or will be destroyed soon), don't update the UI if (isFinishing()) { return; } mContactDetailLayoutController.setContactData(result); } }); } @Override public void onEditRequested(Uri contactLookupUri) { Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri); intent.putExtra( ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true); startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT); } @Override public void onDeleteRequested(Uri contactUri) { ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false); } } public class ContactDetailFragmentListener implements ContactDetailFragment.Listener { @Override public void onItemClicked(Intent intent) { if (intent == null) { return; } try { startActivity(intent); } catch (ActivityNotFoundException e) { Log.e(TAG, "No activity found for intent: " + intent); } } @Override public void onCreateRawContactRequested(ArrayList<ContentValues> values, AccountWithDataSet account) { Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy, Toast.LENGTH_LONG).show(); Intent serviceIntent = ContactSaveService.createNewRawContactIntent( PeopleActivity.this, values, account, PeopleActivity.class, Intent.ACTION_VIEW); startService(serviceIntent); } } private class ContactsUnavailableFragmentListener implements OnContactsUnavailableActionListener { @Override public void onCreateNewContactAction() { startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI)); } @Override public void onAddAccountAction() { Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] { ContactsContract.AUTHORITY }); startActivity(intent); } @Override public void onImportContactsFromFileAction() { ImportExportDialogFragment.show(getFragmentManager()); } @Override public void onFreeInternalStorageAction() { startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)); } } private final class StrequentContactListFragmentListener implements ContactTileListFragment.Listener { @Override public void onContactSelected(Uri contactUri, Rect targetRect) { if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { QuickContact.showQuickContact(PeopleActivity.this, targetRect, contactUri, 0, null); } else { startActivity(new Intent(Intent.ACTION_VIEW, contactUri)); } } } private final class GroupBrowserActionListener implements OnGroupBrowserActionListener { @Override public void onViewGroupAction(Uri groupUri) { if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { setupGroupDetailFragment(groupUri); } else { Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class); intent.setData(groupUri); startActivity(intent); } } } private class GroupDetailFragmentListener implements GroupDetailFragment.Listener { @Override public void onGroupSizeUpdated(String size) { // Nothing needs to be done here because the size will be displayed in the detail // fragment } @Override public void onGroupTitleUpdated(String title) { // Nothing needs to be done here because the title will be displayed in the detail // fragment } @Override public void onAccountTypeUpdated(String accountTypeString, String dataSet) { // Nothing needs to be done here because the group source will be displayed in the // detail fragment } @Override public void onEditRequested(Uri groupUri) { final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); intent.setData(groupUri); intent.setAction(Intent.ACTION_EDIT); startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP); } @Override public void onContactSelected(Uri contactUri) { // Nothing needs to be done here because either quickcontact will be displayed // or activity will take care of selection } } public void startActivityAndForwardResult(final Intent intent) { intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); // Forward extras to the new activity Bundle extras = getIntent().getExtras(); if (extras != null) { intent.putExtras(extras); } startActivity(intent); finish(); } @Override public boolean onCreateOptionsMenu(Menu menu) { if (!areContactsAvailable()) { // If contacts aren't available, hide all menu items. return false; } super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.actions, menu); // On narrow screens we specify a NEW group button in the {@link ActionBar}, so that // it can be in the overflow menu. On wide screens, we use a custom view because we need // its location for anchoring the account-selector popup. final MenuItem addGroup = menu.findItem(R.id.menu_custom_add_group); if (addGroup != null) { mAddGroupImageView = getLayoutInflater().inflate( R.layout.add_group_menu_item, null, false); mAddGroupImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { createNewGroupWithAccountDisambiguation(); } }); addGroup.setActionView(mAddGroupImageView); } return true; } private void invalidateOptionsMenuIfNeeded() { if (isOptionsMenuChanged()) { invalidateOptionsMenu(); } } public boolean isOptionsMenuChanged() { if (mOptionsMenuContactsAvailable != areContactsAvailable()) { return true; } if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) { return true; } if (mContactDetailLoaderFragment != null && mContactDetailLoaderFragment.isOptionsMenuChanged()) { return true; } if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) { return true; } return false; } @Override public boolean onPrepareOptionsMenu(Menu menu) { mOptionsMenuContactsAvailable = areContactsAvailable(); if (!mOptionsMenuContactsAvailable) { return false; } final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact); final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter); MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group); if (addGroupMenu == null) { addGroupMenu = menu.findItem(R.id.menu_custom_add_group); } final boolean isSearchMode = mActionBarAdapter.isSearchMode(); if (isSearchMode) { addContactMenu.setVisible(false); addGroupMenu.setVisible(false); contactsFilterMenu.setVisible(false); } else { switch (mActionBarAdapter.getCurrentTab()) { case FAVORITES: addContactMenu.setVisible(false); addGroupMenu.setVisible(false); contactsFilterMenu.setVisible(false); break; case ALL: addContactMenu.setVisible(true); addGroupMenu.setVisible(false); contactsFilterMenu.setVisible(true); break; case GROUPS: // Do not display the "new group" button if no accounts are available if (areGroupWritableAccountsAvailable()) { addGroupMenu.setVisible(true); } else { addGroupMenu.setVisible(false); } addContactMenu.setVisible(false); contactsFilterMenu.setVisible(false); break; } } final boolean showMiscOptions = !isSearchMode; makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions); makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions); makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions); makeMenuItemVisible(menu, R.id.menu_settings, showMiscOptions && !ContactsPreferenceActivity.isEmpty(this)); return true; } private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) { MenuItem item =menu.findItem(itemId); if (item != null) { item.setVisible(visible); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: { // The home icon on the action bar is pressed if (mActionBarAdapter.isUpShowing()) { // "UP" icon press -- should be treated as "back". onBackPressed(); } return true; } case R.id.menu_settings: { final Intent intent = new Intent(this, ContactsPreferenceActivity.class); // as there is only one section right now, make sure it is selected // on small screens, this also hides the section selector // Due to b/5045558, this code unfortunately only works properly on phones boolean settingsAreMultiPane = getResources().getBoolean( com.android.internal.R.bool.preferences_prefer_dual_pane); if (!settingsAreMultiPane) { intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, DisplayOptionsPreferenceFragment.class.getName()); intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE, R.string.preference_displayOptions); } startActivity(intent); return true; } case R.id.menu_contacts_filter: { AccountFilterUtil.startAccountFilterActivityForResult(this, SUBACTIVITY_ACCOUNT_FILTER); return true; } case R.id.menu_search: { onSearchRequested(); return true; } case R.id.menu_add_contact: { final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); // On 2-pane UI, we can let the editor activity finish itself and return // to this activity to display the new contact. if (PhoneCapabilityTester.isUsingTwoPanes(this)) { intent.putExtra( ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true); startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT); } else { // Otherwise, on 1-pane UI, we need the editor to launch the view contact // intent itself. startActivity(intent); } return true; } case R.id.menu_add_group: { createNewGroupWithAccountDisambiguation(); return true; } case R.id.menu_import_export: { ImportExportDialogFragment.show(getFragmentManager()); return true; } case R.id.menu_accounts: { final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] { ContactsContract.AUTHORITY }); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(intent); return true; } } return false; } private void createNewGroupWithAccountDisambiguation() { final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(this).getAccounts(true); if (accounts.size() <= 1 || mAddGroupImageView == null) { // No account to choose or no control to anchor the popup-menu to // ==> just go straight to the editor which will disambig if necessary final Intent intent = new Intent(this, GroupEditorActivity.class); intent.setAction(Intent.ACTION_INSERT); startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); return; } final ListPopupWindow popup = new ListPopupWindow(this, null); popup.setWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width)); popup.setAnchorView(mAddGroupImageView); // Create a list adapter with all writeable accounts (assume that the writeable accounts all // allow group creation). final AccountsListAdapter adapter = new AccountsListAdapter(this, AccountListFilter.ACCOUNTS_GROUP_WRITABLE); popup.setAdapter(adapter); popup.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { popup.dismiss(); AccountWithDataSet account = adapter.getItem(position); final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); intent.setAction(Intent.ACTION_INSERT); intent.putExtra(Intents.Insert.ACCOUNT, account); intent.putExtra(Intents.Insert.DATA_SET, account.dataSet); startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); } }); popup.setModal(true); popup.show(); } @Override public boolean onSearchRequested() { // Search key pressed. mActionBarAdapter.setSearchMode(true); return true; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case SUBACTIVITY_ACCOUNT_FILTER: { AccountFilterUtil.handleAccountFilterResult( mContactListFilterController, resultCode, data); break; } case SUBACTIVITY_NEW_CONTACT: case SUBACTIVITY_EDIT_CONTACT: { if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) { mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT); mAllFragment.setSelectionRequired(true); mAllFragment.reloadDataAndSetSelectedUri(data.getData()); // Suppress IME if in search mode if (mActionBarAdapter != null) { mActionBarAdapter.clearFocusOnSearchView(); } // No need to change the contact filter mCurrentFilterIsValid = true; } break; } case SUBACTIVITY_NEW_GROUP: case SUBACTIVITY_EDIT_GROUP: { if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) { mRequest.setActionCode(ContactsRequest.ACTION_GROUP); mGroupsFragment.setSelectedUri(data.getData()); } break; } // TODO: Using the new startActivityWithResultFromFragment API this should not be needed // anymore case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER: if (resultCode == RESULT_OK) { mAllFragment.onPickerResult(data); } // TODO fix or remove multipicker code // else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) { // // Finish the activity if the sub activity was canceled as back key is used // // to confirm user selection in MODE_PICK_MULTIPLE_PHONES. // finish(); // } // break; } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO move to the fragment switch (keyCode) { // case KeyEvent.KEYCODE_CALL: { // if (callSelection()) { // return true; // } // break; // } case KeyEvent.KEYCODE_DEL: { if (deleteSelection()) { return true; } break; } default: { // Bring up the search UI if the user starts typing final int unicodeChar = event.getUnicodeChar(); if (unicodeChar != 0 && !Character.isWhitespace(unicodeChar)) { String query = new String(new int[]{ unicodeChar }, 0, 1); if (!mActionBarAdapter.isSearchMode()) { mActionBarAdapter.setQueryString(query); mActionBarAdapter.setSearchMode(true); return true; } } } } return super.onKeyDown(keyCode, event); } @Override public void onBackPressed() { if (mActionBarAdapter.isSearchMode()) { mActionBarAdapter.setSearchMode(false); } else { super.onBackPressed(); } } private boolean deleteSelection() { // TODO move to the fragment // if (mActionCode == ContactsRequest.ACTION_DEFAULT) { // final int position = mListView.getSelectedItemPosition(); // if (position != ListView.INVALID_POSITION) { // Uri contactUri = getContactUri(position); // if (contactUri != null) { // doContactDelete(contactUri); // return true; // } // } // } return false; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mActionBarAdapter.onSaveInstanceState(outState); if (mContactDetailLayoutController != null) { mContactDetailLayoutController.onSaveInstanceState(outState); } // Clear the listener to make sure we don't get callbacks after onSaveInstanceState, // in order to avoid doing fragment transactions after it. // TODO Figure out a better way to deal with the issue. mActionBarAdapter.setListener(null); if (mTabPager != null) { mTabPager.setOnPageChangeListener(null); } } @Override public DialogManager getDialogManager() { return mDialogManager; } // Visible for testing public ContactBrowseListFragment getListFragment() { return mAllFragment; } // Visible for testing public ContactDetailFragment getDetailFragment() { return mContactDetailFragment; } }