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