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