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