| /* |
| * 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.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.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.MenuItem.OnMenuItemClickListener; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.Toast; |
| |
| 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.common.dialog.ClearFrequentsDialog; |
| 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.common.interactions.ImportExportDialogFragment; |
| import com.android.contacts.list.ContactBrowseListFragment; |
| 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.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.common.list.DirectoryListLoader; |
| 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.model.Contact; |
| import com.android.contacts.common.model.account.AccountWithDataSet; |
| import com.android.contacts.preference.ContactsPreferenceActivity; |
| import com.android.contacts.preference.DisplayOptionsPreferenceFragment; |
| import com.android.contacts.common.util.AccountFilterUtil; |
| 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 com.android.contacts.util.PhoneCapabilityTester; |
| import com.android.contacts.common.util.UriUtils; |
| import com.android.contacts.widget.TransitionAnimationView; |
| |
| import java.util.ArrayList; |
| import java.util.Locale; |
| 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"; |
| |
| /** Shows a toogle button for hiding/showing updates. Don't submit with true */ |
| private static final boolean DEBUG_TRANSITIONS = false; |
| |
| private static final int TAB_FADE_IN_DURATION = 500; |
| |
| 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_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 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 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; |
| private ContactTileFrequentFragment mFrequentFragment; |
| private GroupBrowseListFragment mGroupsFragment; |
| |
| private View mFavoritesView; |
| private View mBrowserView; |
| private TransitionAnimationView mPeopleActivityView; |
| private TransitionAnimationView mContactDetailsView; |
| private TransitionAnimationView mGroupDetailsView; |
| |
| /** 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 boolean mEnableDebugMenuOptions; |
| |
| 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); |
| 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, 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 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); |
| getWindow().setBackgroundDrawableResource(R.color.background_primary); |
| 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; |
| } |
| 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. |
| final 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 |
| mPeopleActivityView = getView(R.id.people_view); |
| mFavoritesView = getView(R.id.favorites_view); |
| mContactDetailsView = getView(R.id.contact_details_view); |
| mGroupDetailsView = getView(R.id.group_details_view); |
| mBrowserView = getView(R.id.browse_view); |
| |
| // Only favorites tab with two panes has a separate frequent fragment |
| if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) { |
| 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(), mContactDetailsView, |
| findViewById(R.id.contact_detail_container), |
| new ContactDetailFragmentListener()); |
| } |
| transaction.commitAllowingStateLoss(); |
| fragmentManager.executePendingTransactions(); |
| |
| // Setting Properties after fragment is created |
| if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) { |
| mFavoritesFragment.enableQuickContact(true); |
| mFavoritesFragment.setDisplayType(DisplayType.STARRED_ONLY); |
| } else { |
| // For 2-pane in All and Groups but not in Favorites fragment, show the chevron |
| // for quick contact popup |
| mFavoritesFragment.enableQuickContact(isUsingTwoPanes); |
| 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; |
| 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); |
| 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() { |
| 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: |
| // We redirect this intent to the detail activity on 1-pane, so we don't get |
| // here. It's only for 2-pane. |
| Uri currentlyLoadedContactUri = mContactDetailFragment.getUri(); |
| if (currentlyLoadedContactUri != null |
| && !mRequest.getContactUri().equals(currentlyLoadedContactUri)) { |
| mContactDetailsView.setMaskVisibility(true); |
| } |
| tabToOpen = TabState.ALL; |
| break; |
| case ContactsRequest.ACTION_GROUP: |
| tabToOpen = TabState.GROUPS; |
| 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(); |
| 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) { |
| // If we are switching from one group to another, do a cross-fade |
| if (mGroupDetailFragment != null && mGroupDetailFragment.getGroupUri() != null && |
| !UriUtils.areEqual(mGroupDetailFragment.getGroupUri(), groupUri)) { |
| mGroupDetailsView.startMaskTransition(false, -1); |
| } |
| mGroupDetailFragment.loadGroup(groupUri); |
| 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(); |
| } |
| |
| 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(); |
| |
| // 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); |
| if (mTabPager.getCurrentItem() != tab) { |
| mTabPager.setCurrentItem(tab, !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 TabState.FAVORITES: |
| mFavoritesView.setVisibility(View.VISIBLE); |
| mBrowserView.setVisibility(View.GONE); |
| mGroupDetailsView.setVisibility(View.GONE); |
| mContactDetailsView.setVisibility(View.GONE); |
| break; |
| case TabState.GROUPS: |
| mFavoritesView.setVisibility(View.GONE); |
| mBrowserView.setVisibility(View.VISIBLE); |
| mGroupDetailsView.setVisibility(View.VISIBLE); |
| mContactDetailsView.setVisibility(View.GONE); |
| mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); |
| break; |
| case TabState.ALL: |
| mFavoritesView.setVisibility(View.GONE); |
| mBrowserView.setVisibility(View.VISIBLE); |
| mContactDetailsView.setVisibility(View.VISIBLE); |
| mGroupDetailsView.setVisibility(View.GONE); |
| break; |
| } |
| mPeopleActivityView.startMaskTransition(false, TAB_FADE_IN_DURATION); |
| 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 TabState.FAVORITES: |
| showFragment(ft, mFavoritesFragment); |
| showFragment(ft, mFrequentFragment); |
| hideFragment(ft, mAllFragment); |
| hideFragment(ft, mContactDetailLoaderFragment); |
| hideFragment(ft, mContactDetailFragment); |
| hideFragment(ft, mGroupsFragment); |
| hideFragment(ft, mGroupDetailFragment); |
| break; |
| case TabState.ALL: |
| hideFragment(ft, mFavoritesFragment); |
| hideFragment(ft, mFrequentFragment); |
| showFragment(ft, mAllFragment); |
| showFragment(ft, mContactDetailLoaderFragment); |
| showFragment(ft, mContactDetailFragment); |
| hideFragment(ft, mGroupsFragment); |
| hideFragment(ft, mGroupDetailFragment); |
| break; |
| case TabState.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(int tab) { |
| if (mContactsUnavailableFragment != null) { |
| switch (tab) { |
| case TabState.FAVORITES: |
| mContactsUnavailableFragment.setMessageText( |
| R.string.listTotalAllContactsZeroStarred, -1); |
| break; |
| case TabState.GROUPS: |
| mContactsUnavailableFragment.setMessageText(R.string.noGroups, |
| areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts); |
| break; |
| case TabState.ALL: |
| mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1); |
| break; |
| } |
| } |
| } |
| |
| 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) { |
| } |
| |
| @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()) { |
| mActionBarAdapter.setCurrentTab(position, false); |
| showEmptyStateForTab(position); |
| if (position == 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.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 TabState.FAVORITES; |
| } |
| if (object == mAllFragment) { |
| return TabState.ALL; |
| } |
| if (object == mGroupsFragment) { |
| return TabState.GROUPS; |
| } |
| } |
| return POSITION_NONE; |
| } |
| |
| @Override |
| public void startUpdate(ViewGroup container) { |
| } |
| |
| private Fragment getFragment(int 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; |
| } else if (position == TabState.GROUPS) { |
| return mGroupsFragment; |
| } |
| } |
| 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) { |
| } |
| } |
| |
| 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); |
| final Locale locale = Locale.getDefault(); |
| final int layoutDirection = TextUtils.getLayoutDirectionFromLocale(locale); |
| final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL); |
| final int position; |
| if (useTwoPane) { |
| position = isLayoutRtl ? View.SCROLLBAR_POSITION_RIGHT : View.SCROLLBAR_POSITION_LEFT; |
| } else { |
| position = isLayoutRtl ? View.SCROLLBAR_POSITION_LEFT: View.SCROLLBAR_POSITION_RIGHT; |
| } |
| mAllFragment.setVerticalScrollbarPosition(position); |
| 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() { |
| 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); |
| View mainView = findViewById(R.id.main_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 (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. |
| // |
| // 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.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); |
| } |
| |
| if (mainView != null) { |
| mainView.setVisibility(View.INVISIBLE); |
| } |
| |
| showEmptyStateForTab(mActionBarAdapter.getCurrentTab()); |
| } |
| |
| invalidateOptionsMenuIfNeeded(); |
| } |
| |
| private final class ContactBrowserActionListener implements OnContactBrowserActionListener { |
| ContactBrowserActionListener() {} |
| |
| @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); |
| 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 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 { |
| ContactDetailLoaderFragmentListener() {} |
| |
| @Override |
| public void onContactNotFound() { |
| // Nothing needs to be done here |
| } |
| |
| @Override |
| public void onDetailsLoaded(final Contact 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 { |
| 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) { |
| if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) { |
| QuickContact.showQuickContact(PeopleActivity.this, targetRect, contactUri, 0, null); |
| } else { |
| startActivity(new Intent(Intent.ACTION_VIEW, contactUri)); |
| } |
| } |
| |
| @Override |
| public void onCallNumberDirectly(String phoneNumber) { |
| // No need to call phone number directly from People app. |
| Log.w(TAG, "unexpected invocation of onCallNumberDirectly()"); |
| } |
| } |
| |
| private final class GroupBrowserActionListener implements OnGroupBrowserActionListener { |
| |
| GroupBrowserActionListener() {} |
| |
| @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 { |
| |
| GroupDetailFragmentListener() {} |
| |
| @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.people_options, menu); |
| |
| if (DEBUG_TRANSITIONS && mContactDetailLoaderFragment != null) { |
| final MenuItem toggleSocial = |
| menu.add(mContactDetailLoaderFragment.getLoadStreamItems() ? "less" : "more"); |
| toggleSocial.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); |
| toggleSocial.setOnMenuItemClickListener(new OnMenuItemClickListener() { |
| @Override |
| public boolean onMenuItemClick(MenuItem item) { |
| mContactDetailLoaderFragment.toggleLoadStreamItems(); |
| invalidateOptionsMenu(); |
| return false; |
| } |
| }); |
| } |
| |
| 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; |
| } |
| |
| // Get references to individual menu items in the menu |
| 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); |
| |
| 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) { |
| addContactMenu.setVisible(false); |
| addGroupMenu.setVisible(false); |
| contactsFilterMenu.setVisible(false); |
| clearFrequentsMenu.setVisible(false); |
| helpMenu.setVisible(false); |
| } else { |
| switch (mActionBarAdapter.getCurrentTab()) { |
| case TabState.FAVORITES: |
| addContactMenu.setVisible(false); |
| addGroupMenu.setVisible(false); |
| contactsFilterMenu.setVisible(false); |
| clearFrequentsMenu.setVisible(hasFrequents()); |
| break; |
| case TabState.ALL: |
| addContactMenu.setVisible(true); |
| addGroupMenu.setVisible(false); |
| contactsFilterMenu.setVisible(true); |
| clearFrequentsMenu.setVisible(false); |
| break; |
| case TabState.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); |
| 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() { |
| if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) { |
| return mFrequentFragment.hasFrequents(); |
| } else { |
| 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) { |
| 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.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_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: { |
| createNewGroup(); |
| 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; |
| } |
| |
| private void createNewGroup() { |
| final Intent intent = new Intent(this, GroupEditorActivity.class); |
| intent.setAction(Intent.ACTION_INSERT); |
| startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); |
| } |
| |
| @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.setSelectedContactUri(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) |
| // 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); |
| 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 |
| 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; |
| } |
| |
| // Visible for testing |
| public ContactBrowseListFragment getListFragment() { |
| return mAllFragment; |
| } |
| |
| // Visible for testing |
| public ContactDetailFragment getDetailFragment() { |
| return mContactDetailFragment; |
| } |
| } |