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