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