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