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