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