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