PeopleActivity.java revision 971116e466757434f4979a61d4f58ea0deac1ea4
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            // TODO: Send off an intent with the groups URI, so we don't need to specify
840            // the editor activity class. Then it would be declared as:
841            // new Intent(Intent.ACTION_EDIT, groupUri), SUBACTIVITY_EDIT_GROUP);
842            final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class);
843            intent.setData(groupUri);
844            intent.setAction(Intent.ACTION_EDIT);
845            startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP);
846        }
847    }
848
849    public void startActivityAndForwardResult(final Intent intent) {
850        intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
851
852        // Forward extras to the new activity
853        Bundle extras = getIntent().getExtras();
854        if (extras != null) {
855            intent.putExtras(extras);
856        }
857        startActivity(intent);
858        finish();
859    }
860
861    @Override
862    public boolean onCreatePanelMenu(int featureId, Menu menu) {
863        // No menu if contacts are unavailable
864        if (!areContactsAvailable()) {
865            return false;
866        }
867
868        return super.onCreatePanelMenu(featureId, menu);
869    }
870
871    @Override
872    public boolean onPreparePanel(int featureId, View view, Menu menu) {
873        // No menu if contacts are unavailable
874        if (!areContactsAvailable()) {
875            return false;
876        }
877
878        return super.onPreparePanel(featureId, view, menu);
879    }
880
881    @Override
882    public boolean onCreateOptionsMenu(Menu menu) {
883        if (!areContactsAvailable()) {
884            return false;
885        }
886        super.onCreateOptionsMenu(menu);
887
888        MenuInflater inflater = getMenuInflater();
889        inflater.inflate(R.menu.actions, menu);
890        MenuItem searchMenuItem = menu.findItem(R.id.menu_search);
891        if (searchMenuItem != null && searchMenuItem.getActionView() instanceof SearchView) {
892            SearchView searchView = (SearchView) searchMenuItem.getActionView();
893            searchView.setQueryHint(getString(R.string.hint_findContacts));
894            searchView.setIconifiedByDefault(false);
895
896            if (mActionBarAdapter != null) {
897                mActionBarAdapter.setSearchView(searchView);
898            }
899        }
900
901        // On narrow screens we specify a NEW group button in the {@link ActionBar}, so that
902        // it can be in the overflow menu. On wide screens, we use a custom view because we need
903        // its location for anchoring the account-selector popup.
904        final MenuItem addGroup = menu.findItem(R.id.menu_custom_add_group);
905        if (addGroup != null) {
906            mAddGroupImageView = getLayoutInflater().inflate(
907                    R.layout.add_group_menu_item, null, false);
908            View item = mAddGroupImageView.findViewById(R.id.menu_item);
909            item.setOnClickListener(new OnClickListener() {
910                @Override
911                public void onClick(View v) {
912                    createNewGroupWithAccountDisambiguation();
913                }
914            });
915            addGroup.setActionView(mAddGroupImageView);
916        }
917        return true;
918    }
919
920    private void invalidateOptionsMenuIfNeeded() {
921        if (isOptionsMenuChanged()) {
922            invalidateOptionsMenu();
923        }
924    }
925
926    public boolean isOptionsMenuChanged() {
927        if (mOptionsMenuContactsAvailable != areContactsAvailable()) {
928            return true;
929        }
930
931        if (mListFragment != null && mListFragment.isOptionsMenuChanged()) {
932            return true;
933        }
934
935        if (mContactDetailFragment != null && mContactDetailFragment.isOptionsMenuChanged()) {
936            return true;
937        }
938
939        if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) {
940            return true;
941        }
942
943        return false;
944    }
945
946    @Override
947    public boolean onPrepareOptionsMenu(Menu menu) {
948        mOptionsMenuContactsAvailable = areContactsAvailable();
949        if (!mOptionsMenuContactsAvailable) {
950            return false;
951        }
952
953        final MenuItem searchMenu = menu.findItem(R.id.menu_search);
954        final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact);
955        MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group);
956        if (addGroupMenu == null) {
957            addGroupMenu = menu.findItem(R.id.menu_custom_add_group);
958        }
959
960        if (mActionBarAdapter.isSearchMode()) {
961            addContactMenu.setVisible(false);
962            addGroupMenu.setVisible(false);
963            // If search is normally in the overflow menu, when we are in search
964            // mode, hide this option.
965            if (mActionBarAdapter.isSearchInOverflowMenu()) {
966                searchMenu.setVisible(false);
967            }
968        } else {
969            switch (mSelectedTab) {
970                case FAVORITES:
971                    // TODO: Fall through until we determine what the menu items should be for
972                    // this tab
973                case CONTACTS:
974                    addContactMenu.setVisible(true);
975                    addGroupMenu.setVisible(false);
976                    break;
977                case GROUPS:
978                    addContactMenu.setVisible(false);
979                    addGroupMenu.setVisible(true);
980                    break;
981            }
982        }
983
984        MenuItem settings = menu.findItem(R.id.menu_settings);
985        if (settings != null) {
986            settings.setVisible(!ContactsPreferenceActivity.isEmpty(this));
987        }
988
989        return true;
990    }
991
992    @Override
993    public boolean onOptionsItemSelected(MenuItem item) {
994        switch (item.getItemId()) {
995            case R.id.menu_settings: {
996                final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
997                startActivity(intent);
998                return true;
999            }
1000            case R.id.menu_contacts_filter: {
1001                final Intent intent = new Intent(this, CustomContactListFilterActivity.class);
1002                startActivityForResult(intent, SUBACTIVITY_CUSTOMIZE_FILTER);
1003                return true;
1004            }
1005            case R.id.menu_search: {
1006                onSearchRequested();
1007                return true;
1008            }
1009            case R.id.menu_add_contact: {
1010                final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
1011                startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT);
1012                return true;
1013            }
1014            case R.id.menu_add_group: {
1015                createNewGroupWithAccountDisambiguation();
1016                return true;
1017            }
1018            case R.id.menu_import_export: {
1019                ImportExportDialogFragment.show(getFragmentManager());
1020                return true;
1021            }
1022            case R.id.menu_accounts: {
1023                final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
1024                intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
1025                    ContactsContract.AUTHORITY
1026                });
1027                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1028                startActivity(intent);
1029                return true;
1030            }
1031        }
1032        return false;
1033    }
1034
1035    private void createNewGroupWithAccountDisambiguation() {
1036        final ArrayList<Account> accounts =
1037                AccountTypeManager.getInstance(this).getAccounts(true);
1038        if (accounts.size() <= 1 || mAddGroupImageView == null) {
1039            // No account to choose or no control to anchor the popup-menu to
1040            // ==> just go straight to the editor which will disambig if necessary
1041            final Intent intent = new Intent(this, GroupEditorActivity.class);
1042            intent.setAction(Intent.ACTION_INSERT);
1043            startActivityForResult(intent, SUBACTIVITY_NEW_GROUP);
1044            return;
1045        }
1046
1047        final ListPopupWindow popup = new ListPopupWindow(this, null);
1048        popup.setWidth(getResources().getDimensionPixelSize(R.dimen.account_selector_popup_width));
1049        popup.setAnchorView(mAddGroupImageView);
1050        // Create a list adapter with all writeable accounts (assume that the writeable accounts all
1051        // allow group creation).
1052        final AccountsListAdapter adapter = new AccountsListAdapter(this, true);
1053        popup.setAdapter(adapter);
1054        popup.setOnItemClickListener(new OnItemClickListener() {
1055            @Override
1056            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1057                popup.dismiss();
1058                final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class);
1059                intent.setAction(Intent.ACTION_INSERT);
1060                intent.putExtra(Intents.Insert.ACCOUNT, adapter.getItem(position));
1061                startActivityForResult(intent, SUBACTIVITY_NEW_GROUP);
1062            }
1063        });
1064        popup.setModal(true);
1065        popup.show();
1066    }
1067
1068    @Override
1069    public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
1070            boolean globalSearch) {
1071        if (mListFragment != null && mListFragment.isAdded() && !globalSearch) {
1072            mListFragment.startSearch(initialQuery);
1073        } else {
1074            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1075        }
1076    }
1077
1078    @Override
1079    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1080        switch (requestCode) {
1081            case SUBACTIVITY_CUSTOMIZE_FILTER: {
1082                if (resultCode == Activity.RESULT_OK) {
1083                    mContactListFilterController.selectCustomFilter();
1084                }
1085                break;
1086            }
1087
1088            case SUBACTIVITY_EDIT_CONTACT:
1089            case SUBACTIVITY_NEW_CONTACT: {
1090                if (resultCode == RESULT_OK && mContentPaneDisplayed) {
1091                    mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT);
1092                    mListFragment.reloadDataAndSetSelectedUri(data.getData());
1093                }
1094                break;
1095            }
1096
1097            case SUBACTIVITY_NEW_GROUP:
1098            case SUBACTIVITY_EDIT_GROUP: {
1099                if (resultCode == RESULT_OK && mContentPaneDisplayed) {
1100                    mRequest.setActionCode(ContactsRequest.ACTION_GROUP);
1101                    mGroupsFragment.setSelectedUri(data.getData());
1102                }
1103                break;
1104            }
1105
1106            // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
1107            // anymore
1108            case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
1109                if (resultCode == RESULT_OK) {
1110                    mListFragment.onPickerResult(data);
1111                }
1112
1113// TODO fix or remove multipicker code
1114//                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
1115//                    // Finish the activity if the sub activity was canceled as back key is used
1116//                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
1117//                    finish();
1118//                }
1119//                break;
1120        }
1121    }
1122
1123    @Override
1124    public boolean onContextItemSelected(MenuItem item) {
1125        ContextMenuAdapter menuAdapter = mListFragment.getContextMenuAdapter();
1126        if (menuAdapter != null) {
1127            return menuAdapter.onContextItemSelected(item);
1128        }
1129
1130        return super.onContextItemSelected(item);
1131    }
1132
1133    @Override
1134    public boolean onKeyDown(int keyCode, KeyEvent event) {
1135        // TODO move to the fragment
1136        switch (keyCode) {
1137//            case KeyEvent.KEYCODE_CALL: {
1138//                if (callSelection()) {
1139//                    return true;
1140//                }
1141//                break;
1142//            }
1143
1144            case KeyEvent.KEYCODE_DEL: {
1145                if (deleteSelection()) {
1146                    return true;
1147                }
1148                break;
1149            }
1150            default: {
1151                // Bring up the search UI if the user starts typing
1152                final int unicodeChar = event.getUnicodeChar();
1153                if (unicodeChar != 0 && !Character.isWhitespace(unicodeChar)) {
1154                    String query = new String(new int[]{ unicodeChar }, 0, 1);
1155                    if (!mActionBarAdapter.isSearchMode()) {
1156                        mActionBarAdapter.setQueryString(query);
1157                        mActionBarAdapter.setSearchMode(true);
1158                        return true;
1159                    } else if (!mRequest.isSearchMode()) {
1160                        if (!mSearchInitiated) {
1161                            mSearchInitiated = true;
1162                            startSearch(query, false, null, false);
1163                            return true;
1164                        }
1165                    }
1166                }
1167            }
1168        }
1169
1170        return super.onKeyDown(keyCode, event);
1171    }
1172
1173    @Override
1174    public void onBackPressed() {
1175        if (mSearchMode && mActionBarAdapter != null) {
1176            mActionBarAdapter.setSearchMode(false);
1177        } else {
1178            super.onBackPressed();
1179        }
1180    }
1181
1182    private boolean deleteSelection() {
1183        // TODO move to the fragment
1184//        if (mActionCode == ContactsRequest.ACTION_DEFAULT) {
1185//            final int position = mListView.getSelectedItemPosition();
1186//            if (position != ListView.INVALID_POSITION) {
1187//                Uri contactUri = getContactUri(position);
1188//                if (contactUri != null) {
1189//                    doContactDelete(contactUri);
1190//                    return true;
1191//                }
1192//            }
1193//        }
1194        return false;
1195    }
1196
1197    @Override
1198    protected void onSaveInstanceState(Bundle outState) {
1199        super.onSaveInstanceState(outState);
1200        outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
1201        if (mActionBarAdapter != null) {
1202            mActionBarAdapter.onSaveInstanceState(outState);
1203        }
1204    }
1205
1206    @Override
1207    protected void onRestoreInstanceState(Bundle inState) {
1208        super.onRestoreInstanceState(inState);
1209        mSearchMode = inState.getBoolean(KEY_SEARCH_MODE);
1210        if (mActionBarAdapter != null) {
1211            mActionBarAdapter.onRestoreInstanceState(inState);
1212        }
1213    }
1214
1215    @Override
1216    public DialogManager getDialogManager() {
1217        return mDialogManager;
1218    }
1219
1220    // Visible for testing
1221    public ContactBrowseListFragment getListFragment() {
1222        return mListFragment;
1223    }
1224
1225    // Visible for testing
1226    public ContactDetailFragment getDetailFragment() {
1227        return mContactDetailFragment;
1228    }
1229}
1230