PeopleActivity.java revision 8f59c2b18b73df6af3c808802620839f0a6a61d4
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 searchMode = mActionBarAdapter.isSearchMode();
871        mAllFragment.setSearchMode(searchMode);
872
873        final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this);
874        mAllFragment.setVisibleScrollbarEnabled(!searchMode);
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            ContactListFilter filter;
1028            ContactListFilter currentFilter = mAllFragment.getFilter();
1029            if (currentFilter != null
1030                    && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
1031                filter = ContactListFilter.createFilterWithType(
1032                        ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
1033                mAllFragment.setFilter(filter);
1034            } else {
1035                filter = ContactListFilter.createFilterWithType(
1036                        ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
1037                mAllFragment.setFilter(filter, false);
1038            }
1039            mContactListFilterController.setContactListFilter(filter, true);
1040        }
1041    }
1042
1043    private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener {
1044        @Override
1045        public void onContactNotFound() {
1046            // Nothing needs to be done here
1047        }
1048
1049        @Override
1050        public void onDetailsLoaded(final ContactLoader.Result result) {
1051            if (result == null) {
1052                return;
1053            }
1054            // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the
1055            // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler}
1056            // on the main thread to execute later.
1057            mHandler.post(new Runnable() {
1058                @Override
1059                public void run() {
1060                    // If the activity is destroyed (or will be destroyed soon), don't update the UI
1061                    if (isFinishing()) {
1062                        return;
1063                    }
1064                    mContactDetailLayoutController.setContactData(result);
1065                }
1066            });
1067        }
1068
1069        @Override
1070        public void onEditRequested(Uri contactLookupUri) {
1071            startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri));
1072        }
1073
1074        @Override
1075        public void onDeleteRequested(Uri contactUri) {
1076            ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
1077        }
1078    }
1079
1080    public class ContactDetailFragmentListener implements ContactDetailFragment.Listener {
1081        @Override
1082        public void onItemClicked(Intent intent) {
1083            try {
1084                startActivity(intent);
1085            } catch (ActivityNotFoundException e) {
1086                Log.e(TAG, "No activity found for intent: " + intent);
1087            }
1088        }
1089
1090        @Override
1091        public void onCreateRawContactRequested(ArrayList<ContentValues> values,
1092                AccountWithDataSet account) {
1093            Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy,
1094                    Toast.LENGTH_LONG).show();
1095            Intent serviceIntent = ContactSaveService.createNewRawContactIntent(
1096                    PeopleActivity.this, values, account,
1097                    PeopleActivity.class, Intent.ACTION_VIEW);
1098            startService(serviceIntent);
1099        }
1100    }
1101
1102    private class ContactsUnavailableFragmentListener
1103            implements OnContactsUnavailableActionListener {
1104
1105        @Override
1106        public void onCreateNewContactAction() {
1107            startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
1108        }
1109
1110        @Override
1111        public void onAddAccountAction() {
1112            Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
1113            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1114            intent.putExtra(Settings.EXTRA_AUTHORITIES,
1115                    new String[] { ContactsContract.AUTHORITY });
1116            startActivity(intent);
1117        }
1118
1119        @Override
1120        public void onImportContactsFromFileAction() {
1121            AccountSelectionUtil.doImportFromSdCard(PeopleActivity.this, null);
1122        }
1123
1124        @Override
1125        public void onFreeInternalStorageAction() {
1126            startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
1127        }
1128    }
1129
1130    private final class StrequentContactListFragmentListener
1131            implements ContactTileListFragment.Listener {
1132        @Override
1133        public void onContactSelected(Uri contactUri) {
1134            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
1135                setupContactDetailFragment(contactUri);
1136            } else {
1137                startActivity(new Intent(Intent.ACTION_VIEW, contactUri));
1138            }
1139        }
1140    }
1141
1142    private final class GroupBrowserActionListener implements OnGroupBrowserActionListener {
1143
1144        @Override
1145        public void onViewGroupAction(Uri groupUri) {
1146            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
1147                setupGroupDetailFragment(groupUri);
1148            } else {
1149                Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class);
1150                intent.setData(groupUri);
1151                startActivity(intent);
1152            }
1153        }
1154    }
1155
1156    private class GroupDetailFragmentListener implements GroupDetailFragment.Listener {
1157        @Override
1158        public void onGroupSizeUpdated(String size) {
1159            // Nothing needs to be done here because the size will be displayed in the detail
1160            // fragment
1161        }
1162
1163        @Override
1164        public void onGroupTitleUpdated(String title) {
1165            // Nothing needs to be done here because the title will be displayed in the detail
1166            // fragment
1167        }
1168
1169        @Override
1170        public void onGroupSourceUpdated(String accountTypeString, String dataSet,
1171                String groupSourceAction, String groupSourceUri) {
1172            // Nothing needs to be done here because the group source will be displayed in the
1173            // detail fragment
1174        }
1175
1176        @Override
1177        public void onEditRequested(Uri groupUri) {
1178            final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class);
1179            intent.setData(groupUri);
1180            intent.setAction(Intent.ACTION_EDIT);
1181            startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP);
1182        }
1183
1184        @Override
1185        public void onContactSelected(Uri contactUri) {
1186            // Nothing needs to be done here because either quickcontact will be displayed
1187            // or activity will take care of selection
1188        }
1189    }
1190
1191    public void startActivityAndForwardResult(final Intent intent) {
1192        intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
1193
1194        // Forward extras to the new activity
1195        Bundle extras = getIntent().getExtras();
1196        if (extras != null) {
1197            intent.putExtras(extras);
1198        }
1199        startActivity(intent);
1200        finish();
1201    }
1202
1203    @Override
1204    public boolean onCreateOptionsMenu(Menu menu) {
1205//      STOPSHIP Un-comment it once b/5027071 is fixed.
1206//        if (!areContactsAvailable()) {
1207//            If contacts aren't available, hide all menu items.
1208//            return false;
1209//        }
1210        super.onCreateOptionsMenu(menu);
1211
1212        MenuInflater inflater = getMenuInflater();
1213        inflater.inflate(R.menu.actions, menu);
1214
1215        // On narrow screens we specify a NEW group button in the {@link ActionBar}, so that
1216        // it can be in the overflow menu. On wide screens, we use a custom view because we need
1217        // its location for anchoring the account-selector popup.
1218        final MenuItem addGroup = menu.findItem(R.id.menu_custom_add_group);
1219        if (addGroup != null) {
1220            mAddGroupImageView = getLayoutInflater().inflate(
1221                    R.layout.add_group_menu_item, null, false);
1222            View item = mAddGroupImageView.findViewById(R.id.menu_item);
1223            item.setOnClickListener(new OnClickListener() {
1224                @Override
1225                public void onClick(View v) {
1226                    createNewGroupWithAccountDisambiguation();
1227                }
1228            });
1229            addGroup.setActionView(mAddGroupImageView);
1230        }
1231        return true;
1232    }
1233
1234    private void invalidateOptionsMenuIfNeeded() {
1235        if (isOptionsMenuChanged()) {
1236            invalidateOptionsMenu();
1237        }
1238    }
1239
1240    public boolean isOptionsMenuChanged() {
1241        if (mOptionsMenuContactsAvailable != areContactsAvailable()) {
1242            return true;
1243        }
1244
1245        if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) {
1246            return true;
1247        }
1248
1249        if (mContactDetailLoaderFragment != null &&
1250                mContactDetailLoaderFragment.isOptionsMenuChanged()) {
1251            return true;
1252        }
1253
1254        if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) {
1255            return true;
1256        }
1257
1258        return false;
1259    }
1260
1261    @Override
1262    public boolean onPrepareOptionsMenu(Menu menu) {
1263        mOptionsMenuContactsAvailable = areContactsAvailable();
1264        if (!mOptionsMenuContactsAvailable) {
1265            // STOPSHIP Remove makeAllMenuItemsVisible()when STOPSHIP in onCreateOptionsMenu() is
1266            // fixed.
1267            makeAllMenuItemsVisible(menu, false);
1268            return false;
1269        }
1270        makeAllMenuItemsVisible(menu, true);
1271
1272        final MenuItem searchMenu = menu.findItem(R.id.menu_search);
1273        final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact);
1274        final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter);
1275
1276        MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group);
1277        if (addGroupMenu == null) {
1278            addGroupMenu = menu.findItem(R.id.menu_custom_add_group);
1279        }
1280
1281        if (mActionBarAdapter.isSearchMode()) {
1282            addContactMenu.setVisible(false);
1283            addGroupMenu.setVisible(false);
1284            contactsFilterMenu.setVisible(false);
1285        } else {
1286            switch (mActionBarAdapter.getCurrentTab()) {
1287                case FAVORITES:
1288                    addContactMenu.setVisible(false);
1289                    addGroupMenu.setVisible(false);
1290                    contactsFilterMenu.setVisible(false);
1291                    break;
1292                case ALL:
1293                    addContactMenu.setVisible(true);
1294                    addGroupMenu.setVisible(false);
1295                    contactsFilterMenu.setVisible(true);
1296                    break;
1297                case GROUPS:
1298                    // Do not display the "new group" button if no accounts are available
1299                    if (areAccountsAvailable()) {
1300                        addGroupMenu.setVisible(true);
1301                    } else {
1302                        addGroupMenu.setVisible(false);
1303                    }
1304                    addContactMenu.setVisible(false);
1305                    contactsFilterMenu.setVisible(false);
1306                    break;
1307            }
1308        }
1309
1310        if (searchMenu != null) {
1311            // Don't show the search menu in search mode.
1312            searchMenu.setVisible(!mActionBarAdapter.isSearchMode());
1313        }
1314
1315
1316        MenuItem settings = menu.findItem(R.id.menu_settings);
1317        if (settings != null) {
1318            settings.setVisible(!ContactsPreferenceActivity.isEmpty(this));
1319        }
1320
1321        return true;
1322    }
1323
1324    private void makeAllMenuItemsVisible(Menu menu, boolean visible) {
1325        final int itemCount = menu.size();
1326        for (int i = 0; i < itemCount; i++) {
1327            menu.getItem(i).setVisible(visible);
1328        }
1329    }
1330
1331    @Override
1332    public boolean onOptionsItemSelected(MenuItem item) {
1333        switch (item.getItemId()) {
1334            case android.R.id.home: {
1335                // The home icon on the action bar is pressed
1336                if (mActionBarAdapter.isUpShowing()) {
1337                    // "UP" icon press -- should be treated as "back".
1338                    onBackPressed();
1339                }
1340                return true;
1341            }
1342            case R.id.menu_settings: {
1343                final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
1344                // as there is only one section right now, make sure it is selected
1345                // on small screens, this also hides the section selector
1346                // Due to b/5045558, this code unfortunately only works properly on phones
1347                boolean settingsAreMultiPane = getResources().getBoolean(
1348                        com.android.internal.R.bool.preferences_prefer_dual_pane);
1349                if (!settingsAreMultiPane) {
1350                    intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
1351                            DisplayOptionsPreferenceFragment.class.getName());
1352                    intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE,
1353                            R.string.preference_displayOptions);
1354                }
1355                startActivity(intent);
1356                return true;
1357            }
1358            case R.id.menu_contacts_filter: {
1359                final Intent intent = new Intent(this, AccountFilterActivity.class);
1360                startActivityForResult(intent, SUBACTIVITY_ACCOUNT_FILTER);
1361                return true;
1362            }
1363            case R.id.menu_search: {
1364                onSearchRequested();
1365                return true;
1366            }
1367            case R.id.menu_add_contact: {
1368                final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
1369                startActivity(intent);
1370                return true;
1371            }
1372            case R.id.menu_add_group: {
1373                createNewGroupWithAccountDisambiguation();
1374                return true;
1375            }
1376            case R.id.menu_import_export: {
1377                ImportExportDialogFragment.show(getFragmentManager());
1378                return true;
1379            }
1380            case R.id.menu_accounts: {
1381                final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
1382                intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
1383                    ContactsContract.AUTHORITY
1384                });
1385                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1386                startActivity(intent);
1387                return true;
1388            }
1389        }
1390        return false;
1391    }
1392
1393    private void createNewGroupWithAccountDisambiguation() {
1394        final List<AccountWithDataSet> accounts =
1395                AccountTypeManager.getInstance(this).getAccounts(true);
1396        if (accounts.size() <= 1 || mAddGroupImageView == null) {
1397            // No account to choose or no control to anchor the popup-menu to
1398            // ==> just go straight to the editor which will disambig if necessary
1399            final Intent intent = new Intent(this, GroupEditorActivity.class);
1400            intent.setAction(Intent.ACTION_INSERT);
1401            startActivityForResult(intent, SUBACTIVITY_NEW_GROUP);
1402            return;
1403        }
1404
1405        final ListPopupWindow popup = new ListPopupWindow(this, null);
1406        popup.setWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width));
1407        popup.setAnchorView(mAddGroupImageView);
1408        // Create a list adapter with all writeable accounts (assume that the writeable accounts all
1409        // allow group creation).
1410        final AccountsListAdapter adapter = new AccountsListAdapter(this, true);
1411        popup.setAdapter(adapter);
1412        popup.setOnItemClickListener(new OnItemClickListener() {
1413            @Override
1414            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1415                popup.dismiss();
1416                AccountWithDataSet account = adapter.getItem(position);
1417                final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class);
1418                intent.setAction(Intent.ACTION_INSERT);
1419                intent.putExtra(Intents.Insert.ACCOUNT, account);
1420                intent.putExtra(Intents.Insert.DATA_SET, account.dataSet);
1421                startActivityForResult(intent, SUBACTIVITY_NEW_GROUP);
1422            }
1423        });
1424        popup.setModal(true);
1425        popup.show();
1426    }
1427
1428    @Override
1429    public boolean onSearchRequested() { // Search key pressed.
1430        mActionBarAdapter.setSearchMode(true);
1431        return true;
1432    }
1433
1434    @Override
1435    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1436        switch (requestCode) {
1437            case SUBACTIVITY_ACCOUNT_FILTER: {
1438                if (resultCode == Activity.RESULT_OK) {
1439                    ContactListFilter filter = (ContactListFilter) data.getParcelableExtra(
1440                            AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER);
1441                    if (filter == null) {
1442                        return;
1443                    }
1444                    if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
1445                        mContactListFilterController.selectCustomFilter();
1446                    } else {
1447                        mContactListFilterController.setContactListFilter(filter, true);
1448                    }
1449                }
1450                break;
1451            }
1452
1453            case SUBACTIVITY_NEW_GROUP:
1454            case SUBACTIVITY_EDIT_GROUP: {
1455                if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
1456                    mRequest.setActionCode(ContactsRequest.ACTION_GROUP);
1457                    mGroupsFragment.setSelectedUri(data.getData());
1458                }
1459                break;
1460            }
1461
1462            // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
1463            // anymore
1464            case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
1465                if (resultCode == RESULT_OK) {
1466                    mAllFragment.onPickerResult(data);
1467                }
1468
1469// TODO fix or remove multipicker code
1470//                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
1471//                    // Finish the activity if the sub activity was canceled as back key is used
1472//                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
1473//                    finish();
1474//                }
1475//                break;
1476        }
1477    }
1478
1479    @Override
1480    public boolean onKeyDown(int keyCode, KeyEvent event) {
1481        // TODO move to the fragment
1482        switch (keyCode) {
1483//            case KeyEvent.KEYCODE_CALL: {
1484//                if (callSelection()) {
1485//                    return true;
1486//                }
1487//                break;
1488//            }
1489
1490            case KeyEvent.KEYCODE_DEL: {
1491                if (deleteSelection()) {
1492                    return true;
1493                }
1494                break;
1495            }
1496            default: {
1497                // Bring up the search UI if the user starts typing
1498                final int unicodeChar = event.getUnicodeChar();
1499                if (unicodeChar != 0 && !Character.isWhitespace(unicodeChar)) {
1500                    String query = new String(new int[]{ unicodeChar }, 0, 1);
1501                    if (!mActionBarAdapter.isSearchMode()) {
1502                        mActionBarAdapter.setQueryString(query);
1503                        mActionBarAdapter.setSearchMode(true);
1504                        return true;
1505                    }
1506                }
1507            }
1508        }
1509
1510        return super.onKeyDown(keyCode, event);
1511    }
1512
1513    @Override
1514    public void onBackPressed() {
1515        if (mActionBarAdapter.isSearchMode()) {
1516            mActionBarAdapter.setSearchMode(false);
1517        } else {
1518            super.onBackPressed();
1519        }
1520    }
1521
1522    private boolean deleteSelection() {
1523        // TODO move to the fragment
1524//        if (mActionCode == ContactsRequest.ACTION_DEFAULT) {
1525//            final int position = mListView.getSelectedItemPosition();
1526//            if (position != ListView.INVALID_POSITION) {
1527//                Uri contactUri = getContactUri(position);
1528//                if (contactUri != null) {
1529//                    doContactDelete(contactUri);
1530//                    return true;
1531//                }
1532//            }
1533//        }
1534        return false;
1535    }
1536
1537    @Override
1538    protected void onSaveInstanceState(Bundle outState) {
1539        super.onSaveInstanceState(outState);
1540        mActionBarAdapter.onSaveInstanceState(outState);
1541        if (mContactDetailLayoutController != null) {
1542            mContactDetailLayoutController.onSaveInstanceState(outState);
1543        }
1544
1545        // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
1546        // in order to avoid doing fragment transactions after it.
1547        // TODO Figure out a better way to deal with the issue.
1548        mActionBarAdapter.setListener(null);
1549    }
1550
1551    @Override
1552    public DialogManager getDialogManager() {
1553        return mDialogManager;
1554    }
1555
1556    // Visible for testing
1557    public ContactBrowseListFragment getListFragment() {
1558        return mAllFragment;
1559    }
1560
1561    // Visible for testing
1562    public ContactDetailFragment getDetailFragment() {
1563        return mContactDetailFragment;
1564    }
1565}
1566