| /* |
| * 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 android.app.Fragment; |
| import android.app.FragmentManager; |
| import android.app.FragmentTransaction; |
| import android.content.Intent; |
| import android.graphics.Rect; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Parcelable; |
| import android.os.UserManager; |
| import android.preference.PreferenceActivity; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.Contacts; |
| 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.text.TextUtils; |
| import android.util.Log; |
| import android.view.KeyCharacterMap; |
| import android.view.KeyEvent; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.widget.ImageButton; |
| import android.widget.Toolbar; |
| |
| import com.android.contacts.ContactsActivity; |
| import com.android.contacts.R; |
| import com.android.contacts.activities.ActionBarAdapter.TabState; |
| import com.android.contacts.common.ContactsUtils; |
| import com.android.contacts.common.dialog.ClearFrequentsDialog; |
| import com.android.contacts.interactions.ContactDeletionInteraction; |
| import com.android.contacts.common.interactions.ImportExportDialogFragment; |
| import com.android.contacts.common.list.ContactEntryListFragment; |
| import com.android.contacts.common.list.ContactListFilter; |
| import com.android.contacts.common.list.ContactListFilterController; |
| import com.android.contacts.common.list.ContactTileAdapter.DisplayType; |
| 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.common.list.DirectoryListLoader; |
| import com.android.contacts.common.preference.DisplayOptionsPreferenceFragment; |
| import com.android.contacts.list.OnContactBrowserActionListener; |
| import com.android.contacts.list.OnContactsUnavailableActionListener; |
| import com.android.contacts.list.ProviderStatusWatcher; |
| import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener; |
| import com.android.contacts.common.list.ViewPagerTabs; |
| import com.android.contacts.preference.ContactsPreferenceActivity; |
| import com.android.contacts.common.util.AccountFilterUtil; |
| import com.android.contacts.common.util.ViewUtil; |
| import com.android.contacts.quickcontact.QuickContactActivity; |
| import com.android.contacts.util.AccountPromptUtils; |
| import com.android.contacts.common.util.Constants; |
| import com.android.contacts.util.DialogManager; |
| import com.android.contacts.util.HelpUtils; |
| |
| import java.util.Locale; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Displays a list to browse contacts. |
| */ |
| public class PeopleActivity extends ContactsActivity implements |
| View.OnCreateContextMenuListener, |
| View.OnClickListener, |
| ActionBarAdapter.Listener, |
| DialogManager.DialogShowingViewActivity, |
| ContactListFilterController.ContactListFilterListener, |
| ProviderStatusListener { |
| |
| private static final String TAG = "PeopleActivity"; |
| |
| private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!"; |
| |
| // These values needs to start at 2. See {@link ContactEntryListFragment}. |
| private static final int SUBACTIVITY_ACCOUNT_FILTER = 2; |
| |
| private final DialogManager mDialogManager = new DialogManager(this); |
| |
| private ContactsIntentResolver mIntentResolver; |
| private ContactsRequest mRequest; |
| |
| private ActionBarAdapter mActionBarAdapter; |
| |
| private ContactTileListFragment.Listener mFavoritesFragmentListener = |
| new StrequentContactListFragmentListener(); |
| |
| private ContactListFilterController mContactListFilterController; |
| |
| private ContactsUnavailableFragment mContactsUnavailableFragment; |
| private ProviderStatusWatcher mProviderStatusWatcher; |
| private ProviderStatusWatcher.Status mProviderStatus; |
| |
| private boolean mOptionsMenuContactsAvailable; |
| |
| /** |
| * Showing a list of Contacts. Also used for showing search results in search mode. |
| */ |
| private DefaultContactBrowseListFragment mAllFragment; |
| private ContactTileListFragment mFavoritesFragment; |
| |
| /** ViewPager for swipe */ |
| private ViewPager mTabPager; |
| private ViewPagerTabs mViewPagerTabs; |
| private TabPagerAdapter mTabPagerAdapter; |
| private String[] mTabTitles; |
| private final TabPagerListener mTabPagerListener = new TabPagerListener(); |
| |
| private boolean mEnableDebugMenuOptions; |
| |
| /** |
| * 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; |
| |
| /** |
| * This is to disable {@link #onOptionsItemSelected} when we trying to stop the activity. |
| */ |
| private boolean mDisableOptionItemSelected; |
| |
| /** 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); |
| mProviderStatusWatcher = ProviderStatusWatcher.getInstance(this); |
| } |
| |
| @Override |
| public String toString() { |
| // Shown on logcat |
| return String.format("%s@%d", getClass().getSimpleName(), mInstanceId); |
| } |
| |
| public boolean areContactsAvailable() { |
| return (mProviderStatus != null) |
| && mProviderStatus.status == 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, the {@link ContactsUnavailableFragment} is a special fragment which may not |
| * be in the layout, so we have to do the initialization here. |
| * |
| * The ContactsUnavailableFragment is always created at runtime. |
| */ |
| @Override |
| public void onAttachFragment(Fragment fragment) { |
| if (fragment instanceof ContactsUnavailableFragment) { |
| mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment; |
| 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); |
| |
| mProviderStatusWatcher.addListener(this); |
| |
| mIsRecreatedInstance = (savedState != null); |
| createViewsAndFragments(savedState); |
| |
| if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) { |
| Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish"); |
| } |
| getWindow().setBackgroundDrawable(null); |
| } |
| |
| @Override |
| protected void onNewIntent(Intent intent) { |
| setIntent(intent); |
| if (!processIntent(true)) { |
| finish(); |
| return; |
| } |
| mActionBarAdapter.initialize(null, mRequest); |
| |
| mContactListFilterController.checkFilterValidity(false); |
| |
| // 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) { |
| redirect = new Intent(this, QuickContactActivity.class); |
| redirect.setAction(Intent.ACTION_VIEW); |
| redirect.setData(mRequest.getContactUri()); |
| startActivity(redirect); |
| return false; |
| } |
| return true; |
| } |
| |
| private void createViewsAndFragments(Bundle savedState) { |
| // Disable the ActionBar so that we can use a Toolbar. This needs to be called before |
| // setContentView(). |
| getWindow().requestFeature(Window.FEATURE_NO_TITLE); |
| |
| 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(); |
| |
| mTabTitles = new String[TabState.COUNT]; |
| mTabTitles[TabState.FAVORITES] = getString(R.string.favorites_tab_label); |
| mTabTitles[TabState.ALL] = getString(R.string.all_contacts_tab_label); |
| mTabPager = getView(R.id.tab_pager); |
| mTabPagerAdapter = new TabPagerAdapter(); |
| mTabPager.setAdapter(mTabPagerAdapter); |
| mTabPager.setOnPageChangeListener(mTabPagerListener); |
| |
| // Configure toolbar and toolbar tabs. If in landscape mode, we configure tabs differntly. |
| final Toolbar toolbar = getView(R.id.toolbar); |
| setActionBar(toolbar); |
| final ViewPagerTabs portraitViewPagerTabs |
| = (ViewPagerTabs) findViewById(R.id.lists_pager_header); |
| ViewPagerTabs landscapeViewPagerTabs = null; |
| if (portraitViewPagerTabs == null) { |
| landscapeViewPagerTabs = (ViewPagerTabs) getLayoutInflater().inflate( |
| R.layout.people_activity_tabs_lands, toolbar, /* attachToRoot = */ false); |
| mViewPagerTabs = landscapeViewPagerTabs; |
| } else { |
| mViewPagerTabs = portraitViewPagerTabs; |
| } |
| mViewPagerTabs.setViewPager(mTabPager); |
| |
| final String FAVORITE_TAG = "tab-pager-favorite"; |
| final String ALL_TAG = "tab-pager-all"; |
| |
| // 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); |
| |
| if (mFavoritesFragment == null) { |
| mFavoritesFragment = new ContactTileListFragment(); |
| mAllFragment = new DefaultContactBrowseListFragment(); |
| |
| transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG); |
| transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG); |
| } |
| |
| mFavoritesFragment.setListener(mFavoritesFragmentListener); |
| |
| mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener()); |
| |
| // Hide all fragments for now. We adjust visibility when we get onSelectedTabChanged() |
| // from ActionBarAdapter. |
| transaction.hide(mFavoritesFragment); |
| transaction.hide(mAllFragment); |
| |
| transaction.commitAllowingStateLoss(); |
| fragmentManager.executePendingTransactions(); |
| |
| // Setting Properties after fragment is created |
| mFavoritesFragment.setDisplayType(DisplayType.STREQUENT); |
| |
| mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar(), |
| portraitViewPagerTabs, landscapeViewPagerTabs, toolbar); |
| mActionBarAdapter.initialize(savedState, mRequest); |
| |
| // Add shadow under toolbar |
| ViewUtil.addRectangularOutlineProvider(findViewById(R.id.toolbar_parent), getResources()); |
| |
| // Configure action button |
| final View floatingActionButtonContainer = findViewById( |
| R.id.floating_action_button_container); |
| ViewUtil.setupFloatingActionButton(floatingActionButtonContainer, getResources()); |
| final ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button); |
| floatingActionButton.setOnClickListener(this); |
| |
| 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); |
| } |
| super.onStart(); |
| } |
| |
| @Override |
| protected void onPause() { |
| mOptionsMenuContactsAvailable = false; |
| mProviderStatusWatcher.stop(); |
| super.onPause(); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| |
| mProviderStatusWatcher.start(); |
| updateViewConfiguration(true); |
| |
| // Re-register the listener, which may have been cleared when onSaveInstanceState was |
| // called. See also: onSaveInstanceState |
| mActionBarAdapter.setListener(this); |
| mDisableOptionItemSelected = false; |
| 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(); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| mProviderStatusWatcher.removeListener(this); |
| |
| // 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(); |
| final int tabToOpen; |
| 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: |
| tabToOpen = TabState.ALL; |
| break; |
| default: |
| tabToOpen = -1; |
| break; |
| } |
| if (tabToOpen != -1) { |
| mActionBarAdapter.setCurrentTab(tabToOpen); |
| } |
| |
| if (filter != null) { |
| mContactListFilterController.setContactListFilter(filter, false); |
| searchMode = false; |
| } |
| |
| if (mRequest.getContactUri() != null) { |
| searchMode = false; |
| } |
| |
| mActionBarAdapter.setSearchMode(searchMode); |
| configureContactListFragmentForRequest(); |
| } |
| |
| configureContactListFragment(); |
| |
| invalidateOptionsMenuIfNeeded(); |
| } |
| |
| @Override |
| public void onContactListFilterChanged() { |
| if (mAllFragment == null || !mAllFragment.isAdded()) { |
| return; |
| } |
| |
| mAllFragment.setFilter(mContactListFilterController.getFilter()); |
| |
| invalidateOptionsMenuIfNeeded(); |
| } |
| |
| /** |
| * Handler for action bar actions. |
| */ |
| @Override |
| public void onAction(int action) { |
| switch (action) { |
| case ActionBarAdapter.Listener.Action.START_SEARCH_MODE: |
| // Tell the fragments that we're in the search mode |
| configureFragments(false /* from request */); |
| updateFragmentsVisibility(); |
| invalidateOptionsMenu(); |
| break; |
| case ActionBarAdapter.Listener.Action.STOP_SEARCH_MODE: |
| setQueryTextToFragment(""); |
| updateFragmentsVisibility(); |
| invalidateOptionsMenu(); |
| break; |
| case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY: |
| final String queryString = mActionBarAdapter.getQueryString(); |
| setQueryTextToFragment(queryString); |
| updateDebugOptionsVisibility( |
| ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString)); |
| break; |
| default: |
| throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action); |
| } |
| } |
| |
| @Override |
| public void onSelectedTabChanged() { |
| updateFragmentsVisibility(); |
| } |
| |
| @Override |
| public void onUpButtonPressed() { |
| onBackPressed(); |
| } |
| |
| private void updateDebugOptionsVisibility(boolean visible) { |
| if (mEnableDebugMenuOptions != visible) { |
| mEnableDebugMenuOptions = visible; |
| invalidateOptionsMenu(); |
| } |
| } |
| |
| /** |
| * Updates the fragment/view visibility according to the current mode, such as |
| * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}. |
| */ |
| private void updateFragmentsVisibility() { |
| int tab = mActionBarAdapter.getCurrentTab(); |
| |
| if (mActionBarAdapter.isSearchMode()) { |
| mTabPagerAdapter.setSearchMode(true); |
| } else { |
| // No smooth scrolling if quitting from the search mode. |
| final boolean wasSearchMode = mTabPagerAdapter.isSearchMode(); |
| mTabPagerAdapter.setSearchMode(false); |
| if (mTabPager.getCurrentItem() != tab) { |
| mTabPager.setCurrentItem(tab, !wasSearchMode); |
| } |
| } |
| invalidateOptionsMenu(); |
| showEmptyStateForTab(tab); |
| } |
| |
| private void showEmptyStateForTab(int tab) { |
| if (mContactsUnavailableFragment != null) { |
| switch (getTabPositionForTextDirection(tab)) { |
| case TabState.FAVORITES: |
| mContactsUnavailableFragment.setMessageText( |
| R.string.listTotalAllContactsZeroStarred, -1); |
| break; |
| case TabState.ALL: |
| mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1); |
| break; |
| } |
| // When using the mContactsUnavailableFragment the ViewPager doesn't contain two views. |
| // Therefore, we have to trick the ViewPagerTabs into thinking we have changed tabs |
| // when the mContactsUnavailableFragment changes. Otherwise the tab strip won't move. |
| mViewPagerTabs.onPageScrolled(tab, 0, 0); |
| } |
| } |
| |
| private class TabPagerListener implements ViewPager.OnPageChangeListener { |
| |
| // This package-protected constructor is here because of a possible compiler bug. |
| // PeopleActivity$1.class should be generated due to the private outer/inner class access |
| // needed here. But for some reason, PeopleActivity$1.class is missing. |
| // Since $1 class is needed as a jvm work around to get access to the inner class, |
| // changing the constructor to package-protected or public will solve the problem. |
| // To verify whether $1 class is needed, javap PeopleActivity$TabPagerListener and look for |
| // references to PeopleActivity$1. |
| // |
| // When the constructor is private and PeopleActivity$1.class is missing, proguard will |
| // correctly catch this and throw warnings and error out the build on user/userdebug builds. |
| // |
| // All private inner classes below also need this fix. |
| TabPagerListener() {} |
| |
| @Override |
| public void onPageScrollStateChanged(int state) { |
| if (!mTabPagerAdapter.isSearchMode()) { |
| mViewPagerTabs.onPageScrollStateChanged(state); |
| } |
| } |
| |
| @Override |
| public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { |
| if (!mTabPagerAdapter.isSearchMode()) { |
| mViewPagerTabs.onPageScrolled(position, positionOffset, positionOffsetPixels); |
| } |
| } |
| |
| @Override |
| public void onPageSelected(int position) { |
| // Make sure not in the search mode, in which case position != TabState.ordinal(). |
| if (!mTabPagerAdapter.isSearchMode()) { |
| mActionBarAdapter.setCurrentTab(position, false); |
| mViewPagerTabs.onPageSelected(position); |
| showEmptyStateForTab(position); |
| 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.COUNT; |
| } |
| |
| /** 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 getTabPositionForTextDirection(TabState.FAVORITES); |
| } |
| if (object == mAllFragment) { |
| return getTabPositionForTextDirection(TabState.ALL); |
| } |
| } |
| return POSITION_NONE; |
| } |
| |
| @Override |
| public void startUpdate(ViewGroup container) { |
| } |
| |
| private Fragment getFragment(int position) { |
| position = getTabPositionForTextDirection(position); |
| if (mTabPagerAdapterSearchMode) { |
| if (position != 0) { |
| // This has only been observed in monkey tests. |
| // Let's log this issue, but not crash |
| Log.w(TAG, "Request fragment at position=" + position + ", eventhough we " + |
| "are in search mode"); |
| } |
| return mAllFragment; |
| } else { |
| if (position == TabState.FAVORITES) { |
| return mFavoritesFragment; |
| } else if (position == TabState.ALL) { |
| return mAllFragment; |
| } |
| } |
| throw new IllegalArgumentException("position: " + position); |
| } |
| |
| @Override |
| public Object instantiateItem(ViewGroup 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(ViewGroup container, int position, Object object) { |
| if (mCurTransaction == null) { |
| mCurTransaction = mFragmentManager.beginTransaction(); |
| } |
| mCurTransaction.hide((Fragment) object); |
| } |
| |
| @Override |
| public void finishUpdate(ViewGroup 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(ViewGroup 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) { |
| } |
| |
| @Override |
| public CharSequence getPageTitle(int position) { |
| return mTabTitles[position]; |
| } |
| } |
| |
| private void setQueryTextToFragment(String query) { |
| mAllFragment.setQueryString(query, true); |
| mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode()); |
| } |
| |
| private void configureContactListFragmentForRequest() { |
| Uri contactUri = mRequest.getContactUri(); |
| if (contactUri != null) { |
| 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()); |
| |
| mAllFragment.setVerticalScrollbarPosition(getScrollBarPosition()); |
| mAllFragment.setSelectionVisible(false); |
| } |
| |
| private int getScrollBarPosition() { |
| return isRTL() ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT; |
| } |
| |
| private boolean isRTL() { |
| final Locale locale = Locale.getDefault(); |
| return TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL; |
| } |
| |
| @Override |
| public void onProviderStatusChange() { |
| updateViewConfiguration(false); |
| } |
| |
| private void updateViewConfiguration(boolean forceUpdate) { |
| ProviderStatusWatcher.Status providerStatus = mProviderStatusWatcher.getProviderStatus(); |
| if (!forceUpdate && (mProviderStatus != null) |
| && (providerStatus.status == mProviderStatus.status)) return; |
| mProviderStatus = providerStatus; |
| |
| View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view); |
| |
| if (mProviderStatus.status == ProviderStatus.STATUS_NORMAL) { |
| // Ensure that the mTabPager is visible; we may have made it invisible below. |
| contactsUnavailableView.setVisibility(View.GONE); |
| if (mTabPager != null) { |
| mTabPager.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. |
| // |
| // Also check for ability to modify accounts. In limited user mode, you can't modify |
| // accounts so there is no point sending users to account setup activity. |
| final UserManager userManager = UserManager.get(this); |
| final boolean disallowModifyAccounts = userManager.getUserRestrictions().getBoolean( |
| UserManager.DISALLOW_MODIFY_ACCOUNTS); |
| if (!disallowModifyAccounts && !areContactWritableAccountsAvailable() && |
| AccountPromptUtils.shouldShowAccountPrompt(this)) { |
| AccountPromptUtils.neverShowAccountPromptAgain(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.setOnContactsUnavailableActionListener( |
| new ContactsUnavailableFragmentListener()); |
| getFragmentManager().beginTransaction() |
| .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment) |
| .commitAllowingStateLoss(); |
| } |
| mContactsUnavailableFragment.updateStatus(mProviderStatus); |
| |
| // Show the contactsUnavailableView, and hide the mTabPager so that we don't |
| // see it sliding in underneath the contactsUnavailableView at the edges. |
| contactsUnavailableView.setVisibility(View.VISIBLE); |
| if (mTabPager != null) { |
| mTabPager.setVisibility(View.GONE); |
| } |
| |
| showEmptyStateForTab(mActionBarAdapter.getCurrentTab()); |
| } |
| |
| invalidateOptionsMenuIfNeeded(); |
| } |
| |
| private final class ContactBrowserActionListener implements OnContactBrowserActionListener { |
| ContactBrowserActionListener() {} |
| |
| @Override |
| public void onSelectionChange() { |
| |
| } |
| |
| @Override |
| public void onViewContactAction(Uri contactLookupUri) { |
| Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this, |
| (Rect) null, contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED, null); |
| startActivity(intent); |
| } |
| |
| @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 ContactsUnavailableFragmentListener |
| implements OnContactsUnavailableActionListener { |
| ContactsUnavailableFragmentListener() {} |
| |
| @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(), areContactsAvailable(), |
| PeopleActivity.class); |
| } |
| |
| @Override |
| public void onFreeInternalStorageAction() { |
| startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)); |
| } |
| } |
| |
| private final class StrequentContactListFragmentListener |
| implements ContactTileListFragment.Listener { |
| StrequentContactListFragmentListener() {} |
| |
| @Override |
| public void onContactSelected(Uri contactUri, Rect targetRect) { |
| Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this, |
| targetRect, contactUri, QuickContactActivity.MODE_FULLY_EXPANDED, null); |
| startActivity(intent); |
| } |
| |
| @Override |
| public void onCallNumberDirectly(String phoneNumber) { |
| // No need to call phone number directly from People app. |
| Log.w(TAG, "unexpected invocation of onCallNumberDirectly()"); |
| } |
| } |
| |
| @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.people_options, menu); |
| |
| return true; |
| } |
| |
| private void invalidateOptionsMenuIfNeeded() { |
| if (isOptionsMenuChanged()) { |
| invalidateOptionsMenu(); |
| } |
| } |
| |
| public boolean isOptionsMenuChanged() { |
| if (mOptionsMenuContactsAvailable != areContactsAvailable()) { |
| return true; |
| } |
| |
| if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| mOptionsMenuContactsAvailable = areContactsAvailable(); |
| if (!mOptionsMenuContactsAvailable) { |
| return false; |
| } |
| |
| // Get references to individual menu items in the menu |
| final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter); |
| final MenuItem clearFrequentsMenu = menu.findItem(R.id.menu_clear_frequents); |
| final MenuItem helpMenu = menu.findItem(R.id.menu_help); |
| |
| final boolean isSearchMode = mActionBarAdapter.isSearchMode(); |
| if (isSearchMode) { |
| contactsFilterMenu.setVisible(false); |
| clearFrequentsMenu.setVisible(false); |
| helpMenu.setVisible(false); |
| } else { |
| switch (getTabPositionForTextDirection(mActionBarAdapter.getCurrentTab())) { |
| case TabState.FAVORITES: |
| contactsFilterMenu.setVisible(false); |
| clearFrequentsMenu.setVisible(hasFrequents()); |
| break; |
| case TabState.ALL: |
| contactsFilterMenu.setVisible(true); |
| clearFrequentsMenu.setVisible(false); |
| break; |
| } |
| HelpUtils.prepareHelpMenuItem(this, helpMenu, R.string.help_url_people_main); |
| } |
| 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)); |
| |
| // Debug options need to be visible even in search mode. |
| makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions); |
| |
| return true; |
| } |
| |
| /** |
| * Returns whether there are any frequently contacted people being displayed |
| * @return |
| */ |
| private boolean hasFrequents() { |
| return mFavoritesFragment.hasFrequents(); |
| } |
| |
| 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) { |
| if (mDisableOptionItemSelected) { |
| return false; |
| } |
| |
| 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); |
| // Since there is only one section right now, make sure it is selected on |
| // small screens. |
| intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, |
| DisplayOptionsPreferenceFragment.class.getName()); |
| // By default, the title of the activity should be equivalent to the fragment |
| // title. We set this argument to avoid this. Because of a bug, the following |
| // line isn't necessary. But, once the bug is fixed this may become necessary. |
| // b/5045558 refers to this issue, as well as another. |
| intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE, |
| R.string.activity_title_settings); |
| startActivity(intent); |
| return true; |
| } |
| case R.id.menu_contacts_filter: { |
| AccountFilterUtil.startAccountFilterActivityForResult( |
| this, SUBACTIVITY_ACCOUNT_FILTER, |
| mContactListFilterController.getFilter()); |
| return true; |
| } |
| case R.id.menu_search: { |
| onSearchRequested(); |
| return true; |
| } |
| case R.id.menu_import_export: { |
| ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(), |
| PeopleActivity.class); |
| return true; |
| } |
| case R.id.menu_clear_frequents: { |
| ClearFrequentsDialog.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; |
| } |
| case R.id.export_database: { |
| final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE"); |
| intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); |
| startActivity(intent); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @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; |
| } |
| |
| // 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) |
| // If COMBINING_ACCENT is set, it's not a unicode character. |
| && ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 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); |
| |
| // 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. |
| mDisableOptionItemSelected = true; |
| mActionBarAdapter.setListener(null); |
| if (mTabPager != null) { |
| mTabPager.setOnPageChangeListener(null); |
| } |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Bundle savedInstanceState) { |
| super.onRestoreInstanceState(savedInstanceState); |
| // In our own lifecycle, the focus is saved and restore but later taken away by the |
| // ViewPager. As a hack, we force focus on the SearchView if we know that we are searching. |
| // This fixes the keyboard going away on screen rotation |
| if (mActionBarAdapter.isSearchMode()) { |
| mActionBarAdapter.setFocusOnSearchView(); |
| } |
| } |
| |
| @Override |
| public DialogManager getDialogManager() { |
| return mDialogManager; |
| } |
| |
| @Override |
| public void onClick(View view) { |
| switch (view.getId()) { |
| case R.id.floating_action_button: |
| Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI); |
| Bundle extras = getIntent().getExtras(); |
| if (extras != null) { |
| intent.putExtras(extras); |
| } |
| startActivity(intent); |
| break; |
| default: |
| Log.wtf(TAG, "Unexpected onClick event from " + view); |
| } |
| } |
| |
| /** |
| * Returns the tab position adjusted for the text direction. |
| */ |
| private int getTabPositionForTextDirection(int position) { |
| if (isRTL()) { |
| return TabState.COUNT - 1 - position; |
| } |
| return position; |
| } |
| } |