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 android.app.Fragment;
20import android.app.FragmentManager;
21import android.app.FragmentTransaction;
22import android.content.ActivityNotFoundException;
23import android.content.ContentValues;
24import android.content.Intent;
25import android.graphics.Rect;
26import android.net.Uri;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.Parcelable;
30import android.os.UserManager;
31import android.preference.PreferenceActivity;
32import android.provider.ContactsContract;
33import android.provider.ContactsContract.Contacts;
34import android.provider.ContactsContract.ProviderStatus;
35import android.provider.ContactsContract.QuickContact;
36import android.provider.Settings;
37import android.support.v13.app.FragmentPagerAdapter;
38import android.support.v4.view.PagerAdapter;
39import android.support.v4.view.ViewPager;
40import android.text.TextUtils;
41import android.util.Log;
42import android.view.KeyCharacterMap;
43import android.view.KeyEvent;
44import android.view.Menu;
45import android.view.MenuInflater;
46import android.view.MenuItem;
47import android.view.MenuItem.OnMenuItemClickListener;
48import android.view.View;
49import android.view.ViewGroup;
50import android.widget.Toast;
51
52import com.android.contacts.ContactSaveService;
53import com.android.contacts.ContactsActivity;
54import com.android.contacts.ContactsUtils;
55import com.android.contacts.R;
56import com.android.contacts.activities.ActionBarAdapter.TabState;
57import com.android.contacts.detail.ContactDetailFragment;
58import com.android.contacts.detail.ContactDetailLayoutController;
59import com.android.contacts.detail.ContactDetailUpdatesFragment;
60import com.android.contacts.detail.ContactLoaderFragment;
61import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener;
62import com.android.contacts.common.dialog.ClearFrequentsDialog;
63import com.android.contacts.group.GroupBrowseListFragment;
64import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener;
65import com.android.contacts.group.GroupDetailFragment;
66import com.android.contacts.interactions.ContactDeletionInteraction;
67import com.android.contacts.common.interactions.ImportExportDialogFragment;
68import com.android.contacts.list.ContactBrowseListFragment;
69import com.android.contacts.common.list.ContactEntryListFragment;
70import com.android.contacts.common.list.ContactListFilter;
71import com.android.contacts.common.list.ContactListFilterController;
72import com.android.contacts.common.list.ContactTileAdapter.DisplayType;
73import com.android.contacts.list.ContactTileFrequentFragment;
74import com.android.contacts.list.ContactTileListFragment;
75import com.android.contacts.list.ContactsIntentResolver;
76import com.android.contacts.list.ContactsRequest;
77import com.android.contacts.list.ContactsUnavailableFragment;
78import com.android.contacts.list.DefaultContactBrowseListFragment;
79import com.android.contacts.common.list.DirectoryListLoader;
80import com.android.contacts.list.OnContactBrowserActionListener;
81import com.android.contacts.list.OnContactsUnavailableActionListener;
82import com.android.contacts.list.ProviderStatusWatcher;
83import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
84import com.android.contacts.model.Contact;
85import com.android.contacts.common.model.account.AccountWithDataSet;
86import com.android.contacts.preference.ContactsPreferenceActivity;
87import com.android.contacts.preference.DisplayOptionsPreferenceFragment;
88import com.android.contacts.common.util.AccountFilterUtil;
89import com.android.contacts.util.AccountPromptUtils;
90import com.android.contacts.common.util.Constants;
91import com.android.contacts.util.DialogManager;
92import com.android.contacts.util.HelpUtils;
93import com.android.contacts.util.PhoneCapabilityTester;
94import com.android.contacts.common.util.UriUtils;
95import com.android.contacts.widget.TransitionAnimationView;
96
97import java.util.ArrayList;
98import java.util.Locale;
99import java.util.concurrent.atomic.AtomicInteger;
100
101/**
102 * Displays a list to browse contacts. For xlarge screens, this also displays a detail-pane on
103 * the right.
104 */
105public class PeopleActivity extends ContactsActivity
106        implements View.OnCreateContextMenuListener, ActionBarAdapter.Listener,
107        DialogManager.DialogShowingViewActivity,
108        ContactListFilterController.ContactListFilterListener, ProviderStatusListener {
109
110    private static final String TAG = "PeopleActivity";
111
112    /** Shows a toogle button for hiding/showing updates. Don't submit with true */
113    private static final boolean DEBUG_TRANSITIONS = false;
114
115    private static final int TAB_FADE_IN_DURATION = 500;
116
117    private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
118
119    // These values needs to start at 2. See {@link ContactEntryListFragment}.
120    private static final int SUBACTIVITY_NEW_CONTACT = 2;
121    private static final int SUBACTIVITY_EDIT_CONTACT = 3;
122    private static final int SUBACTIVITY_NEW_GROUP = 4;
123    private static final int SUBACTIVITY_EDIT_GROUP = 5;
124    private static final int SUBACTIVITY_ACCOUNT_FILTER = 6;
125
126    private final DialogManager mDialogManager = new DialogManager(this);
127
128    private ContactsIntentResolver mIntentResolver;
129    private ContactsRequest mRequest;
130
131    private ActionBarAdapter mActionBarAdapter;
132
133    private ContactDetailFragment mContactDetailFragment;
134
135    private ContactLoaderFragment mContactDetailLoaderFragment;
136    private final ContactDetailLoaderFragmentListener mContactDetailLoaderFragmentListener =
137            new ContactDetailLoaderFragmentListener();
138
139    private GroupDetailFragment mGroupDetailFragment;
140    private final GroupDetailFragmentListener mGroupDetailFragmentListener =
141            new GroupDetailFragmentListener();
142
143    private ContactTileListFragment.Listener mFavoritesFragmentListener =
144            new StrequentContactListFragmentListener();
145
146    private ContactListFilterController mContactListFilterController;
147
148    private ContactsUnavailableFragment mContactsUnavailableFragment;
149    private ProviderStatusWatcher mProviderStatusWatcher;
150    private ProviderStatusWatcher.Status mProviderStatus;
151
152    private boolean mOptionsMenuContactsAvailable;
153
154    /**
155     * Showing a list of Contacts. Also used for showing search results in search mode.
156     */
157    private DefaultContactBrowseListFragment mAllFragment;
158    private ContactTileListFragment mFavoritesFragment;
159    private ContactTileFrequentFragment mFrequentFragment;
160    private GroupBrowseListFragment mGroupsFragment;
161
162    private View mFavoritesView;
163    private View mBrowserView;
164    private TransitionAnimationView mPeopleActivityView;
165    private TransitionAnimationView mContactDetailsView;
166    private TransitionAnimationView mGroupDetailsView;
167
168    /** ViewPager for swipe, used only on the phone (i.e. one-pane mode) */
169    private ViewPager mTabPager;
170    private TabPagerAdapter mTabPagerAdapter;
171    private final TabPagerListener mTabPagerListener = new TabPagerListener();
172
173    private ContactDetailLayoutController mContactDetailLayoutController;
174
175    private boolean mEnableDebugMenuOptions;
176
177    private final Handler mHandler = new Handler();
178
179    /**
180     * True if this activity instance is a re-created one.  i.e. set true after orientation change.
181     * This is set in {@link #onCreate} for later use in {@link #onStart}.
182     */
183    private boolean mIsRecreatedInstance;
184
185    /**
186     * If {@link #configureFragments(boolean)} is already called.  Used to avoid calling it twice
187     * in {@link #onStart}.
188     * (This initialization only needs to be done once in onStart() when the Activity was just
189     * created from scratch -- i.e. onCreate() was just called)
190     */
191    private boolean mFragmentInitialized;
192
193    /**
194     * Whether or not the current contact filter is valid or not. We need to do a check on
195     * start of the app to verify that the user is not in single contact mode. If so, we should
196     * dynamically change the filter, unless the incoming intent specifically requested a contact
197     * that should be displayed in that mode.
198     */
199    private boolean mCurrentFilterIsValid;
200
201    /** Sequential ID assigned to each instance; used for logging */
202    private final int mInstanceId;
203    private static final AtomicInteger sNextInstanceId = new AtomicInteger();
204
205    public PeopleActivity() {
206        mInstanceId = sNextInstanceId.getAndIncrement();
207        mIntentResolver = new ContactsIntentResolver(this);
208        mProviderStatusWatcher = ProviderStatusWatcher.getInstance(this);
209    }
210
211    @Override
212    public String toString() {
213        // Shown on logcat
214        return String.format("%s@%d", getClass().getSimpleName(), mInstanceId);
215    }
216
217    public boolean areContactsAvailable() {
218        return (mProviderStatus != null)
219                && mProviderStatus.status == ProviderStatus.STATUS_NORMAL;
220    }
221
222    private boolean areContactWritableAccountsAvailable() {
223        return ContactsUtils.areContactWritableAccountsAvailable(this);
224    }
225
226    private boolean areGroupWritableAccountsAvailable() {
227        return ContactsUtils.areGroupWritableAccountsAvailable(this);
228    }
229
230    /**
231     * Initialize fragments that are (or may not be) in the layout.
232     *
233     * For the fragments that are in the layout, we initialize them in
234     * {@link #createViewsAndFragments(Bundle)} after inflating the layout.
235     *
236     * However, there are special fragments which may not be in the layout, so we have to do the
237     * initialization here.
238     * The target fragments are:
239     * - {@link ContactDetailFragment} and {@link ContactDetailUpdatesFragment}:  They may not be
240     *   in the layout depending on the configuration.  (i.e. portrait)
241     * - {@link ContactsUnavailableFragment}: We always create it at runtime.
242     */
243    @Override
244    public void onAttachFragment(Fragment fragment) {
245        if (fragment instanceof ContactDetailFragment) {
246            mContactDetailFragment = (ContactDetailFragment) fragment;
247        } else if (fragment instanceof ContactsUnavailableFragment) {
248            mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment;
249            mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
250                    new ContactsUnavailableFragmentListener());
251        }
252    }
253
254    @Override
255    protected void onCreate(Bundle savedState) {
256        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
257            Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start");
258        }
259        super.onCreate(savedState);
260
261        if (!processIntent(false)) {
262            finish();
263            return;
264        }
265        mContactListFilterController = ContactListFilterController.getInstance(this);
266        mContactListFilterController.checkFilterValidity(false);
267        mContactListFilterController.addListener(this);
268
269        mProviderStatusWatcher.addListener(this);
270
271        mIsRecreatedInstance = (savedState != null);
272        createViewsAndFragments(savedState);
273        getWindow().setBackgroundDrawableResource(R.color.background_primary);
274        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
275            Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish");
276        }
277    }
278
279    @Override
280    protected void onNewIntent(Intent intent) {
281        setIntent(intent);
282        if (!processIntent(true)) {
283            finish();
284            return;
285        }
286        mActionBarAdapter.initialize(null, mRequest);
287
288        mContactListFilterController.checkFilterValidity(false);
289        mCurrentFilterIsValid = true;
290
291        // Re-configure fragments.
292        configureFragments(true /* from request */);
293        invalidateOptionsMenuIfNeeded();
294    }
295
296    /**
297     * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect
298     * is needed.
299     *
300     * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}.
301     * @return {@code true} if {@link PeopleActivity} should continue running.  {@code false}
302     *         if it shouldn't, in which case the caller should finish() itself and shouldn't do
303     *         farther initialization.
304     */
305    private boolean processIntent(boolean forNewIntent) {
306        // Extract relevant information from the intent
307        mRequest = mIntentResolver.resolveIntent(getIntent());
308        if (Log.isLoggable(TAG, Log.DEBUG)) {
309            Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent
310                    + " intent=" + getIntent() + " request=" + mRequest);
311        }
312        if (!mRequest.isValid()) {
313            setResult(RESULT_CANCELED);
314            return false;
315        }
316
317        Intent redirect = mRequest.getRedirectIntent();
318        if (redirect != null) {
319            // Need to start a different activity
320            startActivity(redirect);
321            return false;
322        }
323
324        if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT
325                && !PhoneCapabilityTester.isUsingTwoPanes(this)) {
326            redirect = new Intent(this, ContactDetailActivity.class);
327            redirect.setAction(Intent.ACTION_VIEW);
328            redirect.setData(mRequest.getContactUri());
329            startActivity(redirect);
330            return false;
331        }
332        return true;
333    }
334
335    private void createViewsAndFragments(Bundle savedState) {
336        setContentView(R.layout.people_activity);
337
338        final FragmentManager fragmentManager = getFragmentManager();
339
340        // Hide all tabs (the current tab will later be reshown once a tab is selected)
341        final FragmentTransaction transaction = fragmentManager.beginTransaction();
342
343        // Prepare the fragments which are used both on 1-pane and on 2-pane.
344        final boolean isUsingTwoPanes = PhoneCapabilityTester.isUsingTwoPanes(this);
345        if (isUsingTwoPanes) {
346            mFavoritesFragment = getFragment(R.id.favorites_fragment);
347            mAllFragment = getFragment(R.id.all_fragment);
348            mGroupsFragment = getFragment(R.id.groups_fragment);
349        } else {
350            mTabPager = getView(R.id.tab_pager);
351            mTabPagerAdapter = new TabPagerAdapter();
352            mTabPager.setAdapter(mTabPagerAdapter);
353            mTabPager.setOnPageChangeListener(mTabPagerListener);
354
355            final String FAVORITE_TAG = "tab-pager-favorite";
356            final String ALL_TAG = "tab-pager-all";
357            final String GROUPS_TAG = "tab-pager-groups";
358
359            // Create the fragments and add as children of the view pager.
360            // The pager adapter will only change the visibility; it'll never create/destroy
361            // fragments.
362            // However, if it's after screen rotation, the fragments have been re-created by
363            // the fragment manager, so first see if there're already the target fragments
364            // existing.
365            mFavoritesFragment = (ContactTileListFragment)
366                    fragmentManager.findFragmentByTag(FAVORITE_TAG);
367            mAllFragment = (DefaultContactBrowseListFragment)
368                    fragmentManager.findFragmentByTag(ALL_TAG);
369            mGroupsFragment = (GroupBrowseListFragment)
370                    fragmentManager.findFragmentByTag(GROUPS_TAG);
371
372            if (mFavoritesFragment == null) {
373                mFavoritesFragment = new ContactTileListFragment();
374                mAllFragment = new DefaultContactBrowseListFragment();
375                mGroupsFragment = new GroupBrowseListFragment();
376
377                transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG);
378                transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG);
379                transaction.add(R.id.tab_pager, mGroupsFragment, GROUPS_TAG);
380            }
381        }
382
383        mFavoritesFragment.setListener(mFavoritesFragmentListener);
384
385        mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener());
386
387        mGroupsFragment.setListener(new GroupBrowserActionListener());
388
389        // Hide all fragments for now.  We adjust visibility when we get onSelectedTabChanged()
390        // from ActionBarAdapter.
391        transaction.hide(mFavoritesFragment);
392        transaction.hide(mAllFragment);
393        transaction.hide(mGroupsFragment);
394
395        if (isUsingTwoPanes) {
396            // Prepare 2-pane only fragments/views...
397
398            // Container views for fragments
399            mPeopleActivityView = getView(R.id.people_view);
400            mFavoritesView = getView(R.id.favorites_view);
401            mContactDetailsView = getView(R.id.contact_details_view);
402            mGroupDetailsView = getView(R.id.group_details_view);
403            mBrowserView = getView(R.id.browse_view);
404
405            // Only favorites tab with two panes has a separate frequent fragment
406            if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) {
407                mFrequentFragment = getFragment(R.id.frequent_fragment);
408                mFrequentFragment.setListener(mFavoritesFragmentListener);
409                mFrequentFragment.setDisplayType(DisplayType.FREQUENT_ONLY);
410                mFrequentFragment.enableQuickContact(true);
411            }
412
413            mContactDetailLoaderFragment = getFragment(R.id.contact_detail_loader_fragment);
414            mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener);
415
416            mGroupDetailFragment = getFragment(R.id.group_detail_fragment);
417            mGroupDetailFragment.setListener(mGroupDetailFragmentListener);
418            mGroupDetailFragment.setQuickContact(true);
419
420            if (mContactDetailFragment != null) {
421                transaction.hide(mContactDetailFragment);
422            }
423            transaction.hide(mGroupDetailFragment);
424
425            // Configure contact details
426            mContactDetailLayoutController = new ContactDetailLayoutController(this, savedState,
427                    getFragmentManager(), mContactDetailsView,
428                    findViewById(R.id.contact_detail_container),
429                    new ContactDetailFragmentListener());
430        }
431        transaction.commitAllowingStateLoss();
432        fragmentManager.executePendingTransactions();
433
434        // Setting Properties after fragment is created
435        if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) {
436            mFavoritesFragment.enableQuickContact(true);
437            mFavoritesFragment.setDisplayType(DisplayType.STARRED_ONLY);
438        } else {
439            // For 2-pane in All and Groups but not in Favorites fragment, show the chevron
440            // for quick contact popup
441            mFavoritesFragment.enableQuickContact(isUsingTwoPanes);
442            mFavoritesFragment.setDisplayType(DisplayType.STREQUENT);
443        }
444
445        // Configure action bar
446        mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar(), isUsingTwoPanes);
447        mActionBarAdapter.initialize(savedState, mRequest);
448
449        invalidateOptionsMenuIfNeeded();
450    }
451
452    @Override
453    protected void onStart() {
454        if (!mFragmentInitialized) {
455            mFragmentInitialized = true;
456            /* Configure fragments if we haven't.
457             *
458             * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}.
459             *
460             * However, because this method may indirectly touch views in fragments but fragments
461             * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT
462             * have views until {@link Activity#onCreate} finishes (they would if they were inflated
463             * from a layout), we need to do it here in {@link #onStart()}.
464             *
465             * (When {@link Fragment#onCreateView} is called is different in the former case and
466             * in the latter case, unfortunately.)
467             *
468             * Also, we skip most of the work in it if the activity is a re-created one.
469             * (so the argument.)
470             */
471            configureFragments(!mIsRecreatedInstance);
472        } else if (PhoneCapabilityTester.isUsingTwoPanes(this) && !mCurrentFilterIsValid) {
473            // We only want to do the filter check in onStart for wide screen devices where it
474            // is often possible to get into single contact mode. Only do this check if
475            // the filter hasn't already been set properly (i.e. onCreate or onActivityResult).
476
477            // Since there is only one {@link ContactListFilterController} across multiple
478            // activity instances, make sure the filter controller is in sync withthe current
479            // contact list fragment filter.
480            // TODO: Clean this up. Perhaps change {@link ContactListFilterController} to not be a
481            // singleton?
482            mContactListFilterController.setContactListFilter(mAllFragment.getFilter(), true);
483            mContactListFilterController.checkFilterValidity(true);
484            mCurrentFilterIsValid = true;
485        }
486        super.onStart();
487    }
488
489    @Override
490    protected void onPause() {
491        mOptionsMenuContactsAvailable = false;
492        mProviderStatusWatcher.stop();
493        super.onPause();
494    }
495
496    @Override
497    protected void onResume() {
498        super.onResume();
499
500        mProviderStatusWatcher.start();
501        updateViewConfiguration(true);
502
503        // Re-register the listener, which may have been cleared when onSaveInstanceState was
504        // called.  See also: onSaveInstanceState
505        mActionBarAdapter.setListener(this);
506        if (mTabPager != null) {
507            mTabPager.setOnPageChangeListener(mTabPagerListener);
508        }
509        // Current tab may have changed since the last onSaveInstanceState().  Make sure
510        // the actual contents match the tab.
511        updateFragmentsVisibility();
512    }
513
514    @Override
515    protected void onStop() {
516        super.onStop();
517        mCurrentFilterIsValid = false;
518    }
519
520    @Override
521    protected void onDestroy() {
522        mProviderStatusWatcher.removeListener(this);
523
524        // Some of variables will be null if this Activity redirects Intent.
525        // See also onCreate() or other methods called during the Activity's initialization.
526        if (mActionBarAdapter != null) {
527            mActionBarAdapter.setListener(null);
528        }
529        if (mContactListFilterController != null) {
530            mContactListFilterController.removeListener(this);
531        }
532
533        super.onDestroy();
534    }
535
536    private void configureFragments(boolean fromRequest) {
537        if (fromRequest) {
538            ContactListFilter filter = null;
539            int actionCode = mRequest.getActionCode();
540            boolean searchMode = mRequest.isSearchMode();
541            final int tabToOpen;
542            switch (actionCode) {
543                case ContactsRequest.ACTION_ALL_CONTACTS:
544                    filter = ContactListFilter.createFilterWithType(
545                            ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
546                    tabToOpen = TabState.ALL;
547                    break;
548                case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
549                    filter = ContactListFilter.createFilterWithType(
550                            ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
551                    tabToOpen = TabState.ALL;
552                    break;
553
554                case ContactsRequest.ACTION_FREQUENT:
555                case ContactsRequest.ACTION_STREQUENT:
556                case ContactsRequest.ACTION_STARRED:
557                    tabToOpen = TabState.FAVORITES;
558                    break;
559                case ContactsRequest.ACTION_VIEW_CONTACT:
560                    // We redirect this intent to the detail activity on 1-pane, so we don't get
561                    // here.  It's only for 2-pane.
562                    Uri currentlyLoadedContactUri = mContactDetailFragment.getUri();
563                    if (currentlyLoadedContactUri != null
564                            && !mRequest.getContactUri().equals(currentlyLoadedContactUri)) {
565                        mContactDetailsView.setMaskVisibility(true);
566                    }
567                    tabToOpen = TabState.ALL;
568                    break;
569                case ContactsRequest.ACTION_GROUP:
570                    tabToOpen = TabState.GROUPS;
571                    break;
572                default:
573                    tabToOpen = -1;
574                    break;
575            }
576            if (tabToOpen != -1) {
577                mActionBarAdapter.setCurrentTab(tabToOpen);
578            }
579
580            if (filter != null) {
581                mContactListFilterController.setContactListFilter(filter, false);
582                searchMode = false;
583            }
584
585            if (mRequest.getContactUri() != null) {
586                searchMode = false;
587            }
588
589            mActionBarAdapter.setSearchMode(searchMode);
590            configureContactListFragmentForRequest();
591        }
592
593        configureContactListFragment();
594        configureGroupListFragment();
595
596        invalidateOptionsMenuIfNeeded();
597    }
598
599    @Override
600    public void onContactListFilterChanged() {
601        if (mAllFragment == null || !mAllFragment.isAdded()) {
602            return;
603        }
604
605        mAllFragment.setFilter(mContactListFilterController.getFilter());
606
607        invalidateOptionsMenuIfNeeded();
608    }
609
610    private void setupContactDetailFragment(final Uri contactLookupUri) {
611        mContactDetailLoaderFragment.loadUri(contactLookupUri);
612        invalidateOptionsMenuIfNeeded();
613    }
614
615    private void setupGroupDetailFragment(Uri groupUri) {
616        // If we are switching from one group to another, do a cross-fade
617        if (mGroupDetailFragment != null && mGroupDetailFragment.getGroupUri() != null &&
618                !UriUtils.areEqual(mGroupDetailFragment.getGroupUri(), groupUri)) {
619            mGroupDetailsView.startMaskTransition(false, -1);
620        }
621        mGroupDetailFragment.loadGroup(groupUri);
622        invalidateOptionsMenuIfNeeded();
623    }
624
625    /**
626     * Handler for action bar actions.
627     */
628    @Override
629    public void onAction(int action) {
630        switch (action) {
631            case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
632                // Tell the fragments that we're in the search mode
633                configureFragments(false /* from request */);
634                updateFragmentsVisibility();
635                invalidateOptionsMenu();
636                break;
637            case ActionBarAdapter.Listener.Action.STOP_SEARCH_MODE:
638                setQueryTextToFragment("");
639                updateFragmentsVisibility();
640                invalidateOptionsMenu();
641                break;
642            case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
643                final String queryString = mActionBarAdapter.getQueryString();
644                setQueryTextToFragment(queryString);
645                updateDebugOptionsVisibility(
646                        ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
647                break;
648            default:
649                throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action);
650        }
651    }
652
653    @Override
654    public void onSelectedTabChanged() {
655        updateFragmentsVisibility();
656    }
657
658    private void updateDebugOptionsVisibility(boolean visible) {
659        if (mEnableDebugMenuOptions != visible) {
660            mEnableDebugMenuOptions = visible;
661            invalidateOptionsMenu();
662        }
663    }
664
665    /**
666     * Updates the fragment/view visibility according to the current mode, such as
667     * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}.
668     */
669    private void updateFragmentsVisibility() {
670        int tab = mActionBarAdapter.getCurrentTab();
671
672        // We use ViewPager on 1-pane.
673        if (!PhoneCapabilityTester.isUsingTwoPanes(this)) {
674            if (mActionBarAdapter.isSearchMode()) {
675                mTabPagerAdapter.setSearchMode(true);
676            } else {
677                // No smooth scrolling if quitting from the search mode.
678                final boolean wasSearchMode = mTabPagerAdapter.isSearchMode();
679                mTabPagerAdapter.setSearchMode(false);
680                if (mTabPager.getCurrentItem() != tab) {
681                    mTabPager.setCurrentItem(tab, !wasSearchMode);
682                }
683            }
684            invalidateOptionsMenu();
685            showEmptyStateForTab(tab);
686            if (tab == TabState.GROUPS) {
687                mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
688            }
689            return;
690        }
691
692        // for the tablet...
693
694        // If in search mode, we use the all list + contact details to show the result.
695        if (mActionBarAdapter.isSearchMode()) {
696            tab = TabState.ALL;
697        }
698
699        switch (tab) {
700            case TabState.FAVORITES:
701                mFavoritesView.setVisibility(View.VISIBLE);
702                mBrowserView.setVisibility(View.GONE);
703                mGroupDetailsView.setVisibility(View.GONE);
704                mContactDetailsView.setVisibility(View.GONE);
705                break;
706            case TabState.GROUPS:
707                mFavoritesView.setVisibility(View.GONE);
708                mBrowserView.setVisibility(View.VISIBLE);
709                mGroupDetailsView.setVisibility(View.VISIBLE);
710                mContactDetailsView.setVisibility(View.GONE);
711                mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
712                break;
713            case TabState.ALL:
714                mFavoritesView.setVisibility(View.GONE);
715                mBrowserView.setVisibility(View.VISIBLE);
716                mContactDetailsView.setVisibility(View.VISIBLE);
717                mGroupDetailsView.setVisibility(View.GONE);
718                break;
719        }
720        mPeopleActivityView.startMaskTransition(false, TAB_FADE_IN_DURATION);
721        FragmentManager fragmentManager = getFragmentManager();
722        FragmentTransaction ft = fragmentManager.beginTransaction();
723
724        // Note mContactDetailLoaderFragment is an invisible fragment, but we still have to show/
725        // hide it so its options menu will be shown/hidden.
726        switch (tab) {
727            case TabState.FAVORITES:
728                showFragment(ft, mFavoritesFragment);
729                showFragment(ft, mFrequentFragment);
730                hideFragment(ft, mAllFragment);
731                hideFragment(ft, mContactDetailLoaderFragment);
732                hideFragment(ft, mContactDetailFragment);
733                hideFragment(ft, mGroupsFragment);
734                hideFragment(ft, mGroupDetailFragment);
735                break;
736            case TabState.ALL:
737                hideFragment(ft, mFavoritesFragment);
738                hideFragment(ft, mFrequentFragment);
739                showFragment(ft, mAllFragment);
740                showFragment(ft, mContactDetailLoaderFragment);
741                showFragment(ft, mContactDetailFragment);
742                hideFragment(ft, mGroupsFragment);
743                hideFragment(ft, mGroupDetailFragment);
744                break;
745            case TabState.GROUPS:
746                hideFragment(ft, mFavoritesFragment);
747                hideFragment(ft, mFrequentFragment);
748                hideFragment(ft, mAllFragment);
749                hideFragment(ft, mContactDetailLoaderFragment);
750                hideFragment(ft, mContactDetailFragment);
751                showFragment(ft, mGroupsFragment);
752                showFragment(ft, mGroupDetailFragment);
753                break;
754        }
755        if (!ft.isEmpty()) {
756            ft.commitAllowingStateLoss();
757            fragmentManager.executePendingTransactions();
758            // When switching tabs, we need to invalidate options menu, but executing a
759            // fragment transaction does it implicitly.  We don't have to call invalidateOptionsMenu
760            // manually.
761        }
762        showEmptyStateForTab(tab);
763    }
764
765    private void showEmptyStateForTab(int tab) {
766        if (mContactsUnavailableFragment != null) {
767            switch (tab) {
768                case TabState.FAVORITES:
769                    mContactsUnavailableFragment.setMessageText(
770                            R.string.listTotalAllContactsZeroStarred, -1);
771                    break;
772                case TabState.GROUPS:
773                    mContactsUnavailableFragment.setMessageText(R.string.noGroups,
774                            areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts);
775                    break;
776                case TabState.ALL:
777                    mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1);
778                    break;
779            }
780        }
781    }
782
783    private class TabPagerListener implements ViewPager.OnPageChangeListener {
784
785        // This package-protected constructor is here because of a possible compiler bug.
786        // PeopleActivity$1.class should be generated due to the private outer/inner class access
787        // needed here.  But for some reason, PeopleActivity$1.class is missing.
788        // Since $1 class is needed as a jvm work around to get access to the inner class,
789        // changing the constructor to package-protected or public will solve the problem.
790        // To verify whether $1 class is needed, javap PeopleActivity$TabPagerListener and look for
791        // references to PeopleActivity$1.
792        //
793        // When the constructor is private and PeopleActivity$1.class is missing, proguard will
794        // correctly catch this and throw warnings and error out the build on user/userdebug builds.
795        //
796        // All private inner classes below also need this fix.
797        TabPagerListener() {}
798
799        @Override
800        public void onPageScrollStateChanged(int state) {
801        }
802
803        @Override
804        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
805        }
806
807        @Override
808        public void onPageSelected(int position) {
809            // Make sure not in the search mode, in which case position != TabState.ordinal().
810            if (!mTabPagerAdapter.isSearchMode()) {
811                mActionBarAdapter.setCurrentTab(position, false);
812                showEmptyStateForTab(position);
813                if (position == TabState.GROUPS) {
814                    mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
815                }
816                invalidateOptionsMenu();
817            }
818        }
819    }
820
821    /**
822     * Adapter for the {@link ViewPager}.  Unlike {@link FragmentPagerAdapter},
823     * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/
824     * {@link #destroyItem} show/hide fragments instead of attaching/detaching.
825     *
826     * In search mode, we always show the "all" fragment, and disable the swipe.  We change the
827     * number of items to 1 to disable the swipe.
828     *
829     * TODO figure out a more straight way to disable swipe.
830     */
831    private class TabPagerAdapter extends PagerAdapter {
832        private final FragmentManager mFragmentManager;
833        private FragmentTransaction mCurTransaction = null;
834
835        private boolean mTabPagerAdapterSearchMode;
836
837        private Fragment mCurrentPrimaryItem;
838
839        public TabPagerAdapter() {
840            mFragmentManager = getFragmentManager();
841        }
842
843        public boolean isSearchMode() {
844            return mTabPagerAdapterSearchMode;
845        }
846
847        public void setSearchMode(boolean searchMode) {
848            if (searchMode == mTabPagerAdapterSearchMode) {
849                return;
850            }
851            mTabPagerAdapterSearchMode = searchMode;
852            notifyDataSetChanged();
853        }
854
855        @Override
856        public int getCount() {
857            return mTabPagerAdapterSearchMode ? 1 : TabState.COUNT;
858        }
859
860        /** Gets called when the number of items changes. */
861        @Override
862        public int getItemPosition(Object object) {
863            if (mTabPagerAdapterSearchMode) {
864                if (object == mAllFragment) {
865                    return 0; // Only 1 page in search mode
866                }
867            } else {
868                if (object == mFavoritesFragment) {
869                    return TabState.FAVORITES;
870                }
871                if (object == mAllFragment) {
872                    return TabState.ALL;
873                }
874                if (object == mGroupsFragment) {
875                    return TabState.GROUPS;
876                }
877            }
878            return POSITION_NONE;
879        }
880
881        @Override
882        public void startUpdate(ViewGroup container) {
883        }
884
885        private Fragment getFragment(int position) {
886            if (mTabPagerAdapterSearchMode) {
887                if (position != 0) {
888                    // This has only been observed in monkey tests.
889                    // Let's log this issue, but not crash
890                    Log.w(TAG, "Request fragment at position=" + position + ", eventhough we " +
891                            "are in search mode");
892                }
893                return mAllFragment;
894            } else {
895                if (position == TabState.FAVORITES) {
896                    return mFavoritesFragment;
897                } else if (position == TabState.ALL) {
898                    return mAllFragment;
899                } else if (position == TabState.GROUPS) {
900                    return mGroupsFragment;
901                }
902            }
903            throw new IllegalArgumentException("position: " + position);
904        }
905
906        @Override
907        public Object instantiateItem(ViewGroup container, int position) {
908            if (mCurTransaction == null) {
909                mCurTransaction = mFragmentManager.beginTransaction();
910            }
911            Fragment f = getFragment(position);
912            mCurTransaction.show(f);
913
914            // Non primary pages are not visible.
915            f.setUserVisibleHint(f == mCurrentPrimaryItem);
916            return f;
917        }
918
919        @Override
920        public void destroyItem(ViewGroup container, int position, Object object) {
921            if (mCurTransaction == null) {
922                mCurTransaction = mFragmentManager.beginTransaction();
923            }
924            mCurTransaction.hide((Fragment) object);
925        }
926
927        @Override
928        public void finishUpdate(ViewGroup container) {
929            if (mCurTransaction != null) {
930                mCurTransaction.commitAllowingStateLoss();
931                mCurTransaction = null;
932                mFragmentManager.executePendingTransactions();
933            }
934        }
935
936        @Override
937        public boolean isViewFromObject(View view, Object object) {
938            return ((Fragment) object).getView() == view;
939        }
940
941        @Override
942        public void setPrimaryItem(ViewGroup container, int position, Object object) {
943            Fragment fragment = (Fragment) object;
944            if (mCurrentPrimaryItem != fragment) {
945                if (mCurrentPrimaryItem != null) {
946                    mCurrentPrimaryItem.setUserVisibleHint(false);
947                }
948                if (fragment != null) {
949                    fragment.setUserVisibleHint(true);
950                }
951                mCurrentPrimaryItem = fragment;
952            }
953        }
954
955        @Override
956        public Parcelable saveState() {
957            return null;
958        }
959
960        @Override
961        public void restoreState(Parcelable state, ClassLoader loader) {
962        }
963    }
964
965    private void setQueryTextToFragment(String query) {
966        mAllFragment.setQueryString(query, true);
967        mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode());
968    }
969
970    private void configureContactListFragmentForRequest() {
971        Uri contactUri = mRequest.getContactUri();
972        if (contactUri != null) {
973            // For an incoming request, explicitly require a selection if we are on 2-pane UI,
974            // (i.e. even if we view the same selected contact, the contact may no longer be
975            // in the list, so we must refresh the list).
976            if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
977                mAllFragment.setSelectionRequired(true);
978            }
979            mAllFragment.setSelectedContactUri(contactUri);
980        }
981
982        mAllFragment.setFilter(mContactListFilterController.getFilter());
983        setQueryTextToFragment(mActionBarAdapter.getQueryString());
984
985        if (mRequest.isDirectorySearchEnabled()) {
986            mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
987        } else {
988            mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
989        }
990    }
991
992    private void configureContactListFragment() {
993        // Filter may be changed when this Activity is in background.
994        mAllFragment.setFilter(mContactListFilterController.getFilter());
995
996        final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this);
997        final Locale locale = Locale.getDefault();
998        final int layoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
999        final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL);
1000        final int position;
1001        if (useTwoPane)  {
1002            position = isLayoutRtl ? View.SCROLLBAR_POSITION_RIGHT : View.SCROLLBAR_POSITION_LEFT;
1003        } else {
1004            position = isLayoutRtl ? View.SCROLLBAR_POSITION_LEFT: View.SCROLLBAR_POSITION_RIGHT;
1005        }
1006        mAllFragment.setVerticalScrollbarPosition(position);
1007        mAllFragment.setSelectionVisible(useTwoPane);
1008        mAllFragment.setQuickContactEnabled(!useTwoPane);
1009    }
1010
1011    private void configureGroupListFragment() {
1012        final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this);
1013        mGroupsFragment.setVerticalScrollbarPosition(
1014                useTwoPane
1015                        ? View.SCROLLBAR_POSITION_LEFT
1016                        : View.SCROLLBAR_POSITION_RIGHT);
1017        mGroupsFragment.setSelectionVisible(useTwoPane);
1018    }
1019
1020    @Override
1021    public void onProviderStatusChange() {
1022        updateViewConfiguration(false);
1023    }
1024
1025    private void updateViewConfiguration(boolean forceUpdate) {
1026        ProviderStatusWatcher.Status providerStatus = mProviderStatusWatcher.getProviderStatus();
1027        if (!forceUpdate && (mProviderStatus != null)
1028                && (providerStatus.status == mProviderStatus.status)) return;
1029        mProviderStatus = providerStatus;
1030
1031        View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view);
1032        View mainView = findViewById(R.id.main_view);
1033
1034        if (mProviderStatus.status == ProviderStatus.STATUS_NORMAL) {
1035            // Ensure that the mTabPager is visible; we may have made it invisible below.
1036            contactsUnavailableView.setVisibility(View.GONE);
1037            if (mTabPager != null) {
1038                mTabPager.setVisibility(View.VISIBLE);
1039            }
1040
1041            if (mainView != null) {
1042                mainView.setVisibility(View.VISIBLE);
1043            }
1044            if (mAllFragment != null) {
1045                mAllFragment.setEnabled(true);
1046            }
1047        } else {
1048            // If there are no accounts on the device and we should show the "no account" prompt
1049            // (based on {@link SharedPreferences}), then launch the account setup activity so the
1050            // user can sign-in or create an account.
1051            //
1052            // Also check for ability to modify accounts.  In limited user mode, you can't modify
1053            // accounts so there is no point sending users to account setup activity.
1054            final UserManager userManager = UserManager.get(this);
1055            final boolean disallowModifyAccounts = userManager.getUserRestrictions().getBoolean(
1056                    UserManager.DISALLOW_MODIFY_ACCOUNTS);
1057            if (!disallowModifyAccounts && !areContactWritableAccountsAvailable() &&
1058                    AccountPromptUtils.shouldShowAccountPrompt(this)) {
1059                AccountPromptUtils.launchAccountPrompt(this);
1060                return;
1061            }
1062
1063            // Otherwise, continue setting up the page so that the user can still use the app
1064            // without an account.
1065            if (mAllFragment != null) {
1066                mAllFragment.setEnabled(false);
1067            }
1068            if (mContactsUnavailableFragment == null) {
1069                mContactsUnavailableFragment = new ContactsUnavailableFragment();
1070                mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
1071                        new ContactsUnavailableFragmentListener());
1072                getFragmentManager().beginTransaction()
1073                        .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment)
1074                        .commitAllowingStateLoss();
1075            }
1076            mContactsUnavailableFragment.updateStatus(mProviderStatus);
1077
1078            // Show the contactsUnavailableView, and hide the mTabPager so that we don't
1079            // see it sliding in underneath the contactsUnavailableView at the edges.
1080            contactsUnavailableView.setVisibility(View.VISIBLE);
1081            if (mTabPager != null) {
1082                mTabPager.setVisibility(View.GONE);
1083            }
1084
1085            if (mainView != null) {
1086                mainView.setVisibility(View.INVISIBLE);
1087            }
1088
1089            showEmptyStateForTab(mActionBarAdapter.getCurrentTab());
1090        }
1091
1092        invalidateOptionsMenuIfNeeded();
1093    }
1094
1095    private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
1096        ContactBrowserActionListener() {}
1097
1098        @Override
1099        public void onSelectionChange() {
1100            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
1101                setupContactDetailFragment(mAllFragment.getSelectedContactUri());
1102            }
1103        }
1104
1105        @Override
1106        public void onViewContactAction(Uri contactLookupUri) {
1107            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
1108                setupContactDetailFragment(contactLookupUri);
1109            } else {
1110                Intent intent = new Intent(Intent.ACTION_VIEW, contactLookupUri);
1111                startActivity(intent);
1112            }
1113        }
1114
1115        @Override
1116        public void onCreateNewContactAction() {
1117            Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
1118            Bundle extras = getIntent().getExtras();
1119            if (extras != null) {
1120                intent.putExtras(extras);
1121            }
1122            startActivity(intent);
1123        }
1124
1125        @Override
1126        public void onEditContactAction(Uri contactLookupUri) {
1127            Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
1128            Bundle extras = getIntent().getExtras();
1129            if (extras != null) {
1130                intent.putExtras(extras);
1131            }
1132            intent.putExtra(
1133                    ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
1134            startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT);
1135        }
1136
1137        @Override
1138        public void onAddToFavoritesAction(Uri contactUri) {
1139            ContentValues values = new ContentValues(1);
1140            values.put(Contacts.STARRED, 1);
1141            getContentResolver().update(contactUri, values, null, null);
1142        }
1143
1144        @Override
1145        public void onRemoveFromFavoritesAction(Uri contactUri) {
1146            ContentValues values = new ContentValues(1);
1147            values.put(Contacts.STARRED, 0);
1148            getContentResolver().update(contactUri, values, null, null);
1149        }
1150
1151        @Override
1152        public void onDeleteContactAction(Uri contactUri) {
1153            ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
1154        }
1155
1156        @Override
1157        public void onFinishAction() {
1158            onBackPressed();
1159        }
1160
1161        @Override
1162        public void onInvalidSelection() {
1163            ContactListFilter filter;
1164            ContactListFilter currentFilter = mAllFragment.getFilter();
1165            if (currentFilter != null
1166                    && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
1167                filter = ContactListFilter.createFilterWithType(
1168                        ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
1169                mAllFragment.setFilter(filter);
1170            } else {
1171                filter = ContactListFilter.createFilterWithType(
1172                        ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
1173                mAllFragment.setFilter(filter, false);
1174            }
1175            mContactListFilterController.setContactListFilter(filter, true);
1176        }
1177    }
1178
1179    private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener {
1180        ContactDetailLoaderFragmentListener() {}
1181
1182        @Override
1183        public void onContactNotFound() {
1184            // Nothing needs to be done here
1185        }
1186
1187        @Override
1188        public void onDetailsLoaded(final Contact result) {
1189            if (result == null) {
1190                // Nothing is loaded. Show empty state.
1191                mContactDetailLayoutController.showEmptyState();
1192                return;
1193            }
1194            // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the
1195            // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler}
1196            // on the main thread to execute later.
1197            mHandler.post(new Runnable() {
1198                @Override
1199                public void run() {
1200                    // If the activity is destroyed (or will be destroyed soon), don't update the UI
1201                    if (isFinishing()) {
1202                        return;
1203                    }
1204                    mContactDetailLayoutController.setContactData(result);
1205                }
1206            });
1207        }
1208
1209        @Override
1210        public void onEditRequested(Uri contactLookupUri) {
1211            Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
1212            intent.putExtra(
1213                    ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
1214            startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT);
1215        }
1216
1217        @Override
1218        public void onDeleteRequested(Uri contactUri) {
1219            ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
1220        }
1221    }
1222
1223    public class ContactDetailFragmentListener implements ContactDetailFragment.Listener {
1224        @Override
1225        public void onItemClicked(Intent intent) {
1226            if (intent == null) {
1227                return;
1228            }
1229            try {
1230                startActivity(intent);
1231            } catch (ActivityNotFoundException e) {
1232                Log.e(TAG, "No activity found for intent: " + intent);
1233            }
1234        }
1235
1236        @Override
1237        public void onCreateRawContactRequested(ArrayList<ContentValues> values,
1238                AccountWithDataSet account) {
1239            Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy,
1240                    Toast.LENGTH_LONG).show();
1241            Intent serviceIntent = ContactSaveService.createNewRawContactIntent(
1242                    PeopleActivity.this, values, account,
1243                    PeopleActivity.class, Intent.ACTION_VIEW);
1244            startService(serviceIntent);
1245        }
1246    }
1247
1248    private class ContactsUnavailableFragmentListener
1249            implements OnContactsUnavailableActionListener {
1250        ContactsUnavailableFragmentListener() {}
1251
1252        @Override
1253        public void onCreateNewContactAction() {
1254            startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
1255        }
1256
1257        @Override
1258        public void onAddAccountAction() {
1259            Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
1260            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1261            intent.putExtra(Settings.EXTRA_AUTHORITIES,
1262                    new String[] { ContactsContract.AUTHORITY });
1263            startActivity(intent);
1264        }
1265
1266        @Override
1267        public void onImportContactsFromFileAction() {
1268            ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
1269                    PeopleActivity.class);
1270        }
1271
1272        @Override
1273        public void onFreeInternalStorageAction() {
1274            startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
1275        }
1276    }
1277
1278    private final class StrequentContactListFragmentListener
1279            implements ContactTileListFragment.Listener {
1280        StrequentContactListFragmentListener() {}
1281
1282        @Override
1283        public void onContactSelected(Uri contactUri, Rect targetRect) {
1284            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
1285                QuickContact.showQuickContact(PeopleActivity.this, targetRect, contactUri, 0, null);
1286            } else {
1287                startActivity(new Intent(Intent.ACTION_VIEW, contactUri));
1288            }
1289        }
1290
1291        @Override
1292        public void onCallNumberDirectly(String phoneNumber) {
1293            // No need to call phone number directly from People app.
1294            Log.w(TAG, "unexpected invocation of onCallNumberDirectly()");
1295        }
1296    }
1297
1298    private final class GroupBrowserActionListener implements OnGroupBrowserActionListener {
1299
1300        GroupBrowserActionListener() {}
1301
1302        @Override
1303        public void onViewGroupAction(Uri groupUri) {
1304            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
1305                setupGroupDetailFragment(groupUri);
1306            } else {
1307                Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class);
1308                intent.setData(groupUri);
1309                startActivity(intent);
1310            }
1311        }
1312    }
1313
1314    private class GroupDetailFragmentListener implements GroupDetailFragment.Listener {
1315
1316        GroupDetailFragmentListener() {}
1317
1318        @Override
1319        public void onGroupSizeUpdated(String size) {
1320            // Nothing needs to be done here because the size will be displayed in the detail
1321            // fragment
1322        }
1323
1324        @Override
1325        public void onGroupTitleUpdated(String title) {
1326            // Nothing needs to be done here because the title will be displayed in the detail
1327            // fragment
1328        }
1329
1330        @Override
1331        public void onAccountTypeUpdated(String accountTypeString, String dataSet) {
1332            // Nothing needs to be done here because the group source will be displayed in the
1333            // detail fragment
1334        }
1335
1336        @Override
1337        public void onEditRequested(Uri groupUri) {
1338            final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class);
1339            intent.setData(groupUri);
1340            intent.setAction(Intent.ACTION_EDIT);
1341            startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP);
1342        }
1343
1344        @Override
1345        public void onContactSelected(Uri contactUri) {
1346            // Nothing needs to be done here because either quickcontact will be displayed
1347            // or activity will take care of selection
1348        }
1349    }
1350
1351    public void startActivityAndForwardResult(final Intent intent) {
1352        intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
1353
1354        // Forward extras to the new activity
1355        Bundle extras = getIntent().getExtras();
1356        if (extras != null) {
1357            intent.putExtras(extras);
1358        }
1359        startActivity(intent);
1360        finish();
1361    }
1362
1363    @Override
1364    public boolean onCreateOptionsMenu(Menu menu) {
1365        if (!areContactsAvailable()) {
1366            // If contacts aren't available, hide all menu items.
1367            return false;
1368        }
1369        super.onCreateOptionsMenu(menu);
1370
1371        MenuInflater inflater = getMenuInflater();
1372        inflater.inflate(R.menu.people_options, menu);
1373
1374        if (DEBUG_TRANSITIONS && mContactDetailLoaderFragment != null) {
1375            final MenuItem toggleSocial =
1376                    menu.add(mContactDetailLoaderFragment.getLoadStreamItems() ? "less" : "more");
1377            toggleSocial.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1378            toggleSocial.setOnMenuItemClickListener(new OnMenuItemClickListener() {
1379                @Override
1380                public boolean onMenuItemClick(MenuItem item) {
1381                    mContactDetailLoaderFragment.toggleLoadStreamItems();
1382                    invalidateOptionsMenu();
1383                    return false;
1384                }
1385            });
1386        }
1387
1388        return true;
1389    }
1390
1391    private void invalidateOptionsMenuIfNeeded() {
1392        if (isOptionsMenuChanged()) {
1393            invalidateOptionsMenu();
1394        }
1395    }
1396
1397    public boolean isOptionsMenuChanged() {
1398        if (mOptionsMenuContactsAvailable != areContactsAvailable()) {
1399            return true;
1400        }
1401
1402        if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) {
1403            return true;
1404        }
1405
1406        if (mContactDetailLoaderFragment != null &&
1407                mContactDetailLoaderFragment.isOptionsMenuChanged()) {
1408            return true;
1409        }
1410
1411        if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) {
1412            return true;
1413        }
1414
1415        return false;
1416    }
1417
1418    @Override
1419    public boolean onPrepareOptionsMenu(Menu menu) {
1420        mOptionsMenuContactsAvailable = areContactsAvailable();
1421        if (!mOptionsMenuContactsAvailable) {
1422            return false;
1423        }
1424
1425        // Get references to individual menu items in the menu
1426        final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact);
1427        final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter);
1428
1429        MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group);
1430
1431        final MenuItem clearFrequentsMenu = menu.findItem(R.id.menu_clear_frequents);
1432        final MenuItem helpMenu = menu.findItem(R.id.menu_help);
1433
1434        final boolean isSearchMode = mActionBarAdapter.isSearchMode();
1435        if (isSearchMode) {
1436            addContactMenu.setVisible(false);
1437            addGroupMenu.setVisible(false);
1438            contactsFilterMenu.setVisible(false);
1439            clearFrequentsMenu.setVisible(false);
1440            helpMenu.setVisible(false);
1441        } else {
1442            switch (mActionBarAdapter.getCurrentTab()) {
1443                case TabState.FAVORITES:
1444                    addContactMenu.setVisible(false);
1445                    addGroupMenu.setVisible(false);
1446                    contactsFilterMenu.setVisible(false);
1447                    clearFrequentsMenu.setVisible(hasFrequents());
1448                    break;
1449                case TabState.ALL:
1450                    addContactMenu.setVisible(true);
1451                    addGroupMenu.setVisible(false);
1452                    contactsFilterMenu.setVisible(true);
1453                    clearFrequentsMenu.setVisible(false);
1454                    break;
1455                case TabState.GROUPS:
1456                    // Do not display the "new group" button if no accounts are available
1457                    if (areGroupWritableAccountsAvailable()) {
1458                        addGroupMenu.setVisible(true);
1459                    } else {
1460                        addGroupMenu.setVisible(false);
1461                    }
1462                    addContactMenu.setVisible(false);
1463                    contactsFilterMenu.setVisible(false);
1464                    clearFrequentsMenu.setVisible(false);
1465                    break;
1466            }
1467            HelpUtils.prepareHelpMenuItem(this, helpMenu, R.string.help_url_people_main);
1468        }
1469        final boolean showMiscOptions = !isSearchMode;
1470        makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions);
1471        makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions);
1472        makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions);
1473        makeMenuItemVisible(menu, R.id.menu_settings,
1474                showMiscOptions && !ContactsPreferenceActivity.isEmpty(this));
1475
1476        // Debug options need to be visible even in search mode.
1477        makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions);
1478
1479        return true;
1480    }
1481
1482    /**
1483     * Returns whether there are any frequently contacted people being displayed
1484     * @return
1485     */
1486    private boolean hasFrequents() {
1487        if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) {
1488            return mFrequentFragment.hasFrequents();
1489        } else {
1490            return mFavoritesFragment.hasFrequents();
1491        }
1492    }
1493
1494    private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
1495        MenuItem item =menu.findItem(itemId);
1496        if (item != null) {
1497            item.setVisible(visible);
1498        }
1499    }
1500
1501    @Override
1502    public boolean onOptionsItemSelected(MenuItem item) {
1503        switch (item.getItemId()) {
1504            case android.R.id.home: {
1505                // The home icon on the action bar is pressed
1506                if (mActionBarAdapter.isUpShowing()) {
1507                    // "UP" icon press -- should be treated as "back".
1508                    onBackPressed();
1509                }
1510                return true;
1511            }
1512            case R.id.menu_settings: {
1513                final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
1514                // as there is only one section right now, make sure it is selected
1515                // on small screens, this also hides the section selector
1516                // Due to b/5045558, this code unfortunately only works properly on phones
1517                boolean settingsAreMultiPane = getResources().getBoolean(
1518                        com.android.internal.R.bool.preferences_prefer_dual_pane);
1519                if (!settingsAreMultiPane) {
1520                    intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
1521                            DisplayOptionsPreferenceFragment.class.getName());
1522                    intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE,
1523                            R.string.activity_title_settings);
1524                }
1525                startActivity(intent);
1526                return true;
1527            }
1528            case R.id.menu_contacts_filter: {
1529                AccountFilterUtil.startAccountFilterActivityForResult(
1530                        this, SUBACTIVITY_ACCOUNT_FILTER,
1531                        mContactListFilterController.getFilter());
1532                return true;
1533            }
1534            case R.id.menu_search: {
1535                onSearchRequested();
1536                return true;
1537            }
1538            case R.id.menu_add_contact: {
1539                final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
1540                // On 2-pane UI, we can let the editor activity finish itself and return
1541                // to this activity to display the new contact.
1542                if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
1543                    intent.putExtra(
1544                            ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED,
1545                            true);
1546                    startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT);
1547                } else {
1548                    // Otherwise, on 1-pane UI, we need the editor to launch the view contact
1549                    // intent itself.
1550                    startActivity(intent);
1551                }
1552                return true;
1553            }
1554            case R.id.menu_add_group: {
1555                createNewGroup();
1556                return true;
1557            }
1558            case R.id.menu_import_export: {
1559                ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
1560                        PeopleActivity.class);
1561                return true;
1562            }
1563            case R.id.menu_clear_frequents: {
1564                ClearFrequentsDialog.show(getFragmentManager());
1565                return true;
1566            }
1567            case R.id.menu_accounts: {
1568                final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
1569                intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
1570                    ContactsContract.AUTHORITY
1571                });
1572                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1573                startActivity(intent);
1574                return true;
1575            }
1576            case R.id.export_database: {
1577                final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
1578                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1579                startActivity(intent);
1580                return true;
1581            }
1582        }
1583        return false;
1584    }
1585
1586    private void createNewGroup() {
1587        final Intent intent = new Intent(this, GroupEditorActivity.class);
1588        intent.setAction(Intent.ACTION_INSERT);
1589        startActivityForResult(intent, SUBACTIVITY_NEW_GROUP);
1590    }
1591
1592    @Override
1593    public boolean onSearchRequested() { // Search key pressed.
1594        mActionBarAdapter.setSearchMode(true);
1595        return true;
1596    }
1597
1598    @Override
1599    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1600        switch (requestCode) {
1601            case SUBACTIVITY_ACCOUNT_FILTER: {
1602                AccountFilterUtil.handleAccountFilterResult(
1603                        mContactListFilterController, resultCode, data);
1604                break;
1605            }
1606
1607            case SUBACTIVITY_NEW_CONTACT:
1608            case SUBACTIVITY_EDIT_CONTACT: {
1609                if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
1610                    mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT);
1611                    mAllFragment.setSelectionRequired(true);
1612                    mAllFragment.setSelectedContactUri(data.getData());
1613                    // Suppress IME if in search mode
1614                    if (mActionBarAdapter != null) {
1615                        mActionBarAdapter.clearFocusOnSearchView();
1616                    }
1617                    // No need to change the contact filter
1618                    mCurrentFilterIsValid = true;
1619                }
1620                break;
1621            }
1622
1623            case SUBACTIVITY_NEW_GROUP:
1624            case SUBACTIVITY_EDIT_GROUP: {
1625                if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
1626                    mRequest.setActionCode(ContactsRequest.ACTION_GROUP);
1627                    mGroupsFragment.setSelectedUri(data.getData());
1628                }
1629                break;
1630            }
1631
1632            // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
1633            // anymore
1634            case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
1635                if (resultCode == RESULT_OK) {
1636                    mAllFragment.onPickerResult(data);
1637                }
1638
1639// TODO fix or remove multipicker code
1640//                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
1641//                    // Finish the activity if the sub activity was canceled as back key is used
1642//                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
1643//                    finish();
1644//                }
1645//                break;
1646        }
1647    }
1648
1649    @Override
1650    public boolean onKeyDown(int keyCode, KeyEvent event) {
1651        // TODO move to the fragment
1652        switch (keyCode) {
1653//            case KeyEvent.KEYCODE_CALL: {
1654//                if (callSelection()) {
1655//                    return true;
1656//                }
1657//                break;
1658//            }
1659
1660            case KeyEvent.KEYCODE_DEL: {
1661                if (deleteSelection()) {
1662                    return true;
1663                }
1664                break;
1665            }
1666            default: {
1667                // Bring up the search UI if the user starts typing
1668                final int unicodeChar = event.getUnicodeChar();
1669                if ((unicodeChar != 0)
1670                        // If COMBINING_ACCENT is set, it's not a unicode character.
1671                        && ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0)
1672                        && !Character.isWhitespace(unicodeChar)) {
1673                    String query = new String(new int[]{ unicodeChar }, 0, 1);
1674                    if (!mActionBarAdapter.isSearchMode()) {
1675                        mActionBarAdapter.setQueryString(query);
1676                        mActionBarAdapter.setSearchMode(true);
1677                        return true;
1678                    }
1679                }
1680            }
1681        }
1682
1683        return super.onKeyDown(keyCode, event);
1684    }
1685
1686    @Override
1687    public void onBackPressed() {
1688        if (mActionBarAdapter.isSearchMode()) {
1689            mActionBarAdapter.setSearchMode(false);
1690        } else {
1691            super.onBackPressed();
1692        }
1693    }
1694
1695    private boolean deleteSelection() {
1696        // TODO move to the fragment
1697//        if (mActionCode == ContactsRequest.ACTION_DEFAULT) {
1698//            final int position = mListView.getSelectedItemPosition();
1699//            if (position != ListView.INVALID_POSITION) {
1700//                Uri contactUri = getContactUri(position);
1701//                if (contactUri != null) {
1702//                    doContactDelete(contactUri);
1703//                    return true;
1704//                }
1705//            }
1706//        }
1707        return false;
1708    }
1709
1710    @Override
1711    protected void onSaveInstanceState(Bundle outState) {
1712        super.onSaveInstanceState(outState);
1713        mActionBarAdapter.onSaveInstanceState(outState);
1714        if (mContactDetailLayoutController != null) {
1715            mContactDetailLayoutController.onSaveInstanceState(outState);
1716        }
1717
1718        // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
1719        // in order to avoid doing fragment transactions after it.
1720        // TODO Figure out a better way to deal with the issue.
1721        mActionBarAdapter.setListener(null);
1722        if (mTabPager != null) {
1723            mTabPager.setOnPageChangeListener(null);
1724        }
1725    }
1726
1727    @Override
1728    protected void onRestoreInstanceState(Bundle savedInstanceState) {
1729        super.onRestoreInstanceState(savedInstanceState);
1730        // In our own lifecycle, the focus is saved and restore but later taken away by the
1731        // ViewPager. As a hack, we force focus on the SearchView if we know that we are searching.
1732        // This fixes the keyboard going away on screen rotation
1733        if (mActionBarAdapter.isSearchMode()) {
1734            mActionBarAdapter.setFocusOnSearchView();
1735        }
1736    }
1737
1738    @Override
1739    public DialogManager getDialogManager() {
1740        return mDialogManager;
1741    }
1742
1743    // Visible for testing
1744    public ContactBrowseListFragment getListFragment() {
1745        return mAllFragment;
1746    }
1747
1748    // Visible for testing
1749    public ContactDetailFragment getDetailFragment() {
1750        return mContactDetailFragment;
1751    }
1752}
1753