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