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