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