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