PeopleActivity.java revision 5b221086e4b994d6325d7396625900a79e27ae51
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.Intent;
23import android.graphics.Rect;
24import android.net.Uri;
25import android.os.Bundle;
26import android.os.Parcelable;
27import android.os.UserManager;
28import android.preference.PreferenceActivity;
29import android.provider.ContactsContract;
30import android.provider.ContactsContract.Contacts;
31import android.provider.ContactsContract.ProviderStatus;
32import android.provider.ContactsContract.QuickContact;
33import android.provider.Settings;
34import android.support.v13.app.FragmentPagerAdapter;
35import android.support.v4.view.PagerAdapter;
36import android.support.v4.view.ViewPager;
37import android.text.TextUtils;
38import android.util.Log;
39import android.view.KeyCharacterMap;
40import android.view.KeyEvent;
41import android.view.Menu;
42import android.view.MenuInflater;
43import android.view.MenuItem;
44import android.view.View;
45import android.view.ViewGroup;
46import android.widget.ImageButton;
47
48import com.android.contacts.ContactsActivity;
49import com.android.contacts.R;
50import com.android.contacts.activities.ActionBarAdapter.TabState;
51import com.android.contacts.common.ContactsUtils;
52import com.android.contacts.common.dialog.ClearFrequentsDialog;
53import com.android.contacts.interactions.ContactDeletionInteraction;
54import com.android.contacts.common.interactions.ImportExportDialogFragment;
55import com.android.contacts.common.list.ContactEntryListFragment;
56import com.android.contacts.common.list.ContactListFilter;
57import com.android.contacts.common.list.ContactListFilterController;
58import com.android.contacts.common.list.ContactTileAdapter.DisplayType;
59import com.android.contacts.list.ContactTileListFragment;
60import com.android.contacts.list.ContactsIntentResolver;
61import com.android.contacts.list.ContactsRequest;
62import com.android.contacts.list.ContactsUnavailableFragment;
63import com.android.contacts.list.DefaultContactBrowseListFragment;
64import com.android.contacts.common.list.DirectoryListLoader;
65import com.android.contacts.list.OnContactBrowserActionListener;
66import com.android.contacts.list.OnContactsUnavailableActionListener;
67import com.android.contacts.list.ProviderStatusWatcher;
68import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
69import com.android.contacts.preference.ContactsPreferenceActivity;
70import com.android.contacts.preference.DisplayOptionsPreferenceFragment;
71import com.android.contacts.common.util.AccountFilterUtil;
72import com.android.contacts.common.util.ViewUtil;
73import com.android.contacts.quickcontact.QuickContactActivity;
74import com.android.contacts.util.AccountPromptUtils;
75import com.android.contacts.common.util.Constants;
76import com.android.contacts.util.DialogManager;
77import com.android.contacts.util.HelpUtils;
78
79import java.util.Locale;
80import java.util.concurrent.atomic.AtomicInteger;
81
82/**
83 * Displays a list to browse contacts.
84 */
85public class PeopleActivity extends ContactsActivity implements
86        View.OnCreateContextMenuListener,
87        View.OnClickListener,
88        ActionBarAdapter.Listener,
89        DialogManager.DialogShowingViewActivity,
90        ContactListFilterController.ContactListFilterListener,
91        ProviderStatusListener {
92
93    private static final String TAG = "PeopleActivity";
94
95    private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
96
97    // These values needs to start at 2. See {@link ContactEntryListFragment}.
98    private static final int SUBACTIVITY_ACCOUNT_FILTER = 2;
99
100    private final DialogManager mDialogManager = new DialogManager(this);
101
102    private ContactsIntentResolver mIntentResolver;
103    private ContactsRequest mRequest;
104
105    private ActionBarAdapter mActionBarAdapter;
106
107    private ContactTileListFragment.Listener mFavoritesFragmentListener =
108            new StrequentContactListFragmentListener();
109
110    private ContactListFilterController mContactListFilterController;
111
112    private ContactsUnavailableFragment mContactsUnavailableFragment;
113    private ProviderStatusWatcher mProviderStatusWatcher;
114    private ProviderStatusWatcher.Status mProviderStatus;
115
116    private boolean mOptionsMenuContactsAvailable;
117
118    /**
119     * Showing a list of Contacts. Also used for showing search results in search mode.
120     */
121    private DefaultContactBrowseListFragment mAllFragment;
122    private ContactTileListFragment mFavoritesFragment;
123
124    /** ViewPager for swipe */
125    private ViewPager mTabPager;
126    private TabPagerAdapter mTabPagerAdapter;
127    private final TabPagerListener mTabPagerListener = new TabPagerListener();
128
129    private boolean mEnableDebugMenuOptions;
130
131    /**
132     * True if this activity instance is a re-created one.  i.e. set true after orientation change.
133     * This is set in {@link #onCreate} for later use in {@link #onStart}.
134     */
135    private boolean mIsRecreatedInstance;
136
137    /**
138     * If {@link #configureFragments(boolean)} is already called.  Used to avoid calling it twice
139     * in {@link #onStart}.
140     * (This initialization only needs to be done once in onStart() when the Activity was just
141     * created from scratch -- i.e. onCreate() was just called)
142     */
143    private boolean mFragmentInitialized;
144
145    /**
146     * This is to disable {@link #onOptionsItemSelected} when we trying to stop the activity.
147     */
148    private boolean mDisableOptionItemSelected;
149
150    /** Sequential ID assigned to each instance; used for logging */
151    private final int mInstanceId;
152    private static final AtomicInteger sNextInstanceId = new AtomicInteger();
153
154    public PeopleActivity() {
155        mInstanceId = sNextInstanceId.getAndIncrement();
156        mIntentResolver = new ContactsIntentResolver(this);
157        mProviderStatusWatcher = ProviderStatusWatcher.getInstance(this);
158    }
159
160    @Override
161    public String toString() {
162        // Shown on logcat
163        return String.format("%s@%d", getClass().getSimpleName(), mInstanceId);
164    }
165
166    public boolean areContactsAvailable() {
167        return (mProviderStatus != null)
168                && mProviderStatus.status == ProviderStatus.STATUS_NORMAL;
169    }
170
171    private boolean areContactWritableAccountsAvailable() {
172        return ContactsUtils.areContactWritableAccountsAvailable(this);
173    }
174
175    private boolean areGroupWritableAccountsAvailable() {
176        return ContactsUtils.areGroupWritableAccountsAvailable(this);
177    }
178
179    /**
180     * Initialize fragments that are (or may not be) in the layout.
181     *
182     * For the fragments that are in the layout, we initialize them in
183     * {@link #createViewsAndFragments(Bundle)} after inflating the layout.
184     *
185     * However, the {@link ContactsUnavailableFragment} is a special fragment which may not
186     * be in the layout, so we have to do the initialization here.
187     *
188     * The ContactsUnavailableFragment is always created at runtime.
189     */
190    @Override
191    public void onAttachFragment(Fragment fragment) {
192        if (fragment instanceof ContactsUnavailableFragment) {
193            mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment;
194            mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
195                    new ContactsUnavailableFragmentListener());
196        }
197    }
198
199    @Override
200    protected void onCreate(Bundle savedState) {
201        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
202            Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start");
203        }
204        super.onCreate(savedState);
205
206        if (!processIntent(false)) {
207            finish();
208            return;
209        }
210        mContactListFilterController = ContactListFilterController.getInstance(this);
211        mContactListFilterController.checkFilterValidity(false);
212        mContactListFilterController.addListener(this);
213
214        mProviderStatusWatcher.addListener(this);
215
216        mIsRecreatedInstance = (savedState != null);
217        createViewsAndFragments(savedState);
218
219        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
220            Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish");
221        }
222        getWindow().setBackgroundDrawable(null);
223    }
224
225    @Override
226    protected void onNewIntent(Intent intent) {
227        setIntent(intent);
228        if (!processIntent(true)) {
229            finish();
230            return;
231        }
232        mActionBarAdapter.initialize(null, mRequest);
233
234        mContactListFilterController.checkFilterValidity(false);
235
236        // Re-configure fragments.
237        configureFragments(true /* from request */);
238        invalidateOptionsMenuIfNeeded();
239    }
240
241    /**
242     * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect
243     * is needed.
244     *
245     * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}.
246     * @return {@code true} if {@link PeopleActivity} should continue running.  {@code false}
247     *         if it shouldn't, in which case the caller should finish() itself and shouldn't do
248     *         farther initialization.
249     */
250    private boolean processIntent(boolean forNewIntent) {
251        // Extract relevant information from the intent
252        mRequest = mIntentResolver.resolveIntent(getIntent());
253        if (Log.isLoggable(TAG, Log.DEBUG)) {
254            Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent
255                    + " intent=" + getIntent() + " request=" + mRequest);
256        }
257        if (!mRequest.isValid()) {
258            setResult(RESULT_CANCELED);
259            return false;
260        }
261
262        Intent redirect = mRequest.getRedirectIntent();
263        if (redirect != null) {
264            // Need to start a different activity
265            startActivity(redirect);
266            return false;
267        }
268
269        if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT) {
270            redirect = new Intent(this, ContactDetailActivity.class);
271            redirect.setAction(Intent.ACTION_VIEW);
272            redirect.setData(mRequest.getContactUri());
273            startActivity(redirect);
274            return false;
275        }
276        return true;
277    }
278
279    private void createViewsAndFragments(Bundle savedState) {
280        setContentView(R.layout.people_activity);
281
282        final FragmentManager fragmentManager = getFragmentManager();
283
284        // Hide all tabs (the current tab will later be reshown once a tab is selected)
285        final FragmentTransaction transaction = fragmentManager.beginTransaction();
286
287        mTabPager = getView(R.id.tab_pager);
288        mTabPagerAdapter = new TabPagerAdapter();
289        mTabPager.setAdapter(mTabPagerAdapter);
290        mTabPager.setOnPageChangeListener(mTabPagerListener);
291
292        final String FAVORITE_TAG = "tab-pager-favorite";
293        final String ALL_TAG = "tab-pager-all";
294
295        // Create the fragments and add as children of the view pager.
296        // The pager adapter will only change the visibility; it'll never create/destroy
297        // fragments.
298        // However, if it's after screen rotation, the fragments have been re-created by
299        // the fragment manager, so first see if there're already the target fragments
300        // existing.
301        mFavoritesFragment = (ContactTileListFragment)
302                fragmentManager.findFragmentByTag(FAVORITE_TAG);
303        mAllFragment = (DefaultContactBrowseListFragment)
304                fragmentManager.findFragmentByTag(ALL_TAG);
305
306        if (mFavoritesFragment == null) {
307            mFavoritesFragment = new ContactTileListFragment();
308            mAllFragment = new DefaultContactBrowseListFragment();
309
310            transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG);
311            transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG);
312        }
313
314        mFavoritesFragment.setListener(mFavoritesFragmentListener);
315
316        mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener());
317
318        // Hide all fragments for now.  We adjust visibility when we get onSelectedTabChanged()
319        // from ActionBarAdapter.
320        transaction.hide(mFavoritesFragment);
321        transaction.hide(mAllFragment);
322
323        transaction.commitAllowingStateLoss();
324        fragmentManager.executePendingTransactions();
325
326        // Setting Properties after fragment is created
327        mFavoritesFragment.setDisplayType(DisplayType.STREQUENT);
328
329        // Configure action bar
330        mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar());
331        mActionBarAdapter.initialize(savedState, mRequest);
332
333        // Configure action button
334        final View floatingActionButtonContainer = findViewById(
335                R.id.floating_action_button_container);
336        ViewUtil.setupFloatingActionButton(floatingActionButtonContainer, getResources());
337        final ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
338        floatingActionButton.setOnClickListener(this);
339
340        invalidateOptionsMenuIfNeeded();
341    }
342
343    @Override
344    protected void onStart() {
345        if (!mFragmentInitialized) {
346            mFragmentInitialized = true;
347            /* Configure fragments if we haven't.
348             *
349             * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}.
350             *
351             * However, because this method may indirectly touch views in fragments but fragments
352             * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT
353             * have views until {@link Activity#onCreate} finishes (they would if they were inflated
354             * from a layout), we need to do it here in {@link #onStart()}.
355             *
356             * (When {@link Fragment#onCreateView} is called is different in the former case and
357             * in the latter case, unfortunately.)
358             *
359             * Also, we skip most of the work in it if the activity is a re-created one.
360             * (so the argument.)
361             */
362            configureFragments(!mIsRecreatedInstance);
363        }
364        super.onStart();
365    }
366
367    @Override
368    protected void onPause() {
369        mOptionsMenuContactsAvailable = false;
370        mProviderStatusWatcher.stop();
371        super.onPause();
372    }
373
374    @Override
375    protected void onResume() {
376        super.onResume();
377
378        mProviderStatusWatcher.start();
379        updateViewConfiguration(true);
380
381        // Re-register the listener, which may have been cleared when onSaveInstanceState was
382        // called.  See also: onSaveInstanceState
383        mActionBarAdapter.setListener(this);
384        mDisableOptionItemSelected = false;
385        if (mTabPager != null) {
386            mTabPager.setOnPageChangeListener(mTabPagerListener);
387        }
388        // Current tab may have changed since the last onSaveInstanceState().  Make sure
389        // the actual contents match the tab.
390        updateFragmentsVisibility();
391    }
392
393    @Override
394    protected void onStop() {
395        super.onStop();
396    }
397
398    @Override
399    protected void onDestroy() {
400        mProviderStatusWatcher.removeListener(this);
401
402        // Some of variables will be null if this Activity redirects Intent.
403        // See also onCreate() or other methods called during the Activity's initialization.
404        if (mActionBarAdapter != null) {
405            mActionBarAdapter.setListener(null);
406        }
407        if (mContactListFilterController != null) {
408            mContactListFilterController.removeListener(this);
409        }
410
411        super.onDestroy();
412    }
413
414    private void configureFragments(boolean fromRequest) {
415        if (fromRequest) {
416            ContactListFilter filter = null;
417            int actionCode = mRequest.getActionCode();
418            boolean searchMode = mRequest.isSearchMode();
419            final int tabToOpen;
420            switch (actionCode) {
421                case ContactsRequest.ACTION_ALL_CONTACTS:
422                    filter = ContactListFilter.createFilterWithType(
423                            ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
424                    tabToOpen = TabState.ALL;
425                    break;
426                case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
427                    filter = ContactListFilter.createFilterWithType(
428                            ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
429                    tabToOpen = TabState.ALL;
430                    break;
431
432                case ContactsRequest.ACTION_FREQUENT:
433                case ContactsRequest.ACTION_STREQUENT:
434                case ContactsRequest.ACTION_STARRED:
435                    tabToOpen = TabState.FAVORITES;
436                    break;
437                case ContactsRequest.ACTION_VIEW_CONTACT:
438                    tabToOpen = TabState.ALL;
439                    break;
440                default:
441                    tabToOpen = -1;
442                    break;
443            }
444            if (tabToOpen != -1) {
445                mActionBarAdapter.setCurrentTab(tabToOpen);
446            }
447
448            if (filter != null) {
449                mContactListFilterController.setContactListFilter(filter, false);
450                searchMode = false;
451            }
452
453            if (mRequest.getContactUri() != null) {
454                searchMode = false;
455            }
456
457            mActionBarAdapter.setSearchMode(searchMode);
458            configureContactListFragmentForRequest();
459        }
460
461        configureContactListFragment();
462
463        invalidateOptionsMenuIfNeeded();
464    }
465
466    @Override
467    public void onContactListFilterChanged() {
468        if (mAllFragment == null || !mAllFragment.isAdded()) {
469            return;
470        }
471
472        mAllFragment.setFilter(mContactListFilterController.getFilter());
473
474        invalidateOptionsMenuIfNeeded();
475    }
476
477    /**
478     * Handler for action bar actions.
479     */
480    @Override
481    public void onAction(int action) {
482        switch (action) {
483            case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
484                // Tell the fragments that we're in the search mode
485                configureFragments(false /* from request */);
486                updateFragmentsVisibility();
487                invalidateOptionsMenu();
488                break;
489            case ActionBarAdapter.Listener.Action.STOP_SEARCH_MODE:
490                setQueryTextToFragment("");
491                updateFragmentsVisibility();
492                invalidateOptionsMenu();
493                break;
494            case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
495                final String queryString = mActionBarAdapter.getQueryString();
496                setQueryTextToFragment(queryString);
497                updateDebugOptionsVisibility(
498                        ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
499                break;
500            default:
501                throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action);
502        }
503    }
504
505    @Override
506    public void onSelectedTabChanged() {
507        updateFragmentsVisibility();
508    }
509
510    @Override
511    public void onUpButtonPressed() {
512        onBackPressed();
513    }
514
515    private void updateDebugOptionsVisibility(boolean visible) {
516        if (mEnableDebugMenuOptions != visible) {
517            mEnableDebugMenuOptions = visible;
518            invalidateOptionsMenu();
519        }
520    }
521
522    /**
523     * Updates the fragment/view visibility according to the current mode, such as
524     * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}.
525     */
526    private void updateFragmentsVisibility() {
527        int tab = mActionBarAdapter.getCurrentTab();
528
529        if (mActionBarAdapter.isSearchMode()) {
530            mTabPagerAdapter.setSearchMode(true);
531        } else {
532            // No smooth scrolling if quitting from the search mode.
533            final boolean wasSearchMode = mTabPagerAdapter.isSearchMode();
534            mTabPagerAdapter.setSearchMode(false);
535            if (mTabPager.getCurrentItem() != tab) {
536                mTabPager.setCurrentItem(tab, !wasSearchMode);
537            }
538        }
539        invalidateOptionsMenu();
540        showEmptyStateForTab(tab);
541    }
542
543    private void showEmptyStateForTab(int tab) {
544        if (mContactsUnavailableFragment != null) {
545            switch (tab) {
546                case TabState.FAVORITES:
547                    mContactsUnavailableFragment.setMessageText(
548                            R.string.listTotalAllContactsZeroStarred, -1);
549                    break;
550                case TabState.ALL:
551                    mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1);
552                    break;
553            }
554        }
555    }
556
557    private class TabPagerListener implements ViewPager.OnPageChangeListener {
558
559        // This package-protected constructor is here because of a possible compiler bug.
560        // PeopleActivity$1.class should be generated due to the private outer/inner class access
561        // needed here.  But for some reason, PeopleActivity$1.class is missing.
562        // Since $1 class is needed as a jvm work around to get access to the inner class,
563        // changing the constructor to package-protected or public will solve the problem.
564        // To verify whether $1 class is needed, javap PeopleActivity$TabPagerListener and look for
565        // references to PeopleActivity$1.
566        //
567        // When the constructor is private and PeopleActivity$1.class is missing, proguard will
568        // correctly catch this and throw warnings and error out the build on user/userdebug builds.
569        //
570        // All private inner classes below also need this fix.
571        TabPagerListener() {}
572
573        @Override
574        public void onPageScrollStateChanged(int state) {
575        }
576
577        @Override
578        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
579        }
580
581        @Override
582        public void onPageSelected(int position) {
583            // Make sure not in the search mode, in which case position != TabState.ordinal().
584            if (!mTabPagerAdapter.isSearchMode()) {
585                mActionBarAdapter.setCurrentTab(position, false);
586                showEmptyStateForTab(position);
587                invalidateOptionsMenu();
588            }
589        }
590    }
591
592    /**
593     * Adapter for the {@link ViewPager}.  Unlike {@link FragmentPagerAdapter},
594     * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/
595     * {@link #destroyItem} show/hide fragments instead of attaching/detaching.
596     *
597     * In search mode, we always show the "all" fragment, and disable the swipe.  We change the
598     * number of items to 1 to disable the swipe.
599     *
600     * TODO figure out a more straight way to disable swipe.
601     */
602    private class TabPagerAdapter extends PagerAdapter {
603        private final FragmentManager mFragmentManager;
604        private FragmentTransaction mCurTransaction = null;
605
606        private boolean mTabPagerAdapterSearchMode;
607
608        private Fragment mCurrentPrimaryItem;
609
610        public TabPagerAdapter() {
611            mFragmentManager = getFragmentManager();
612        }
613
614        public boolean isSearchMode() {
615            return mTabPagerAdapterSearchMode;
616        }
617
618        public void setSearchMode(boolean searchMode) {
619            if (searchMode == mTabPagerAdapterSearchMode) {
620                return;
621            }
622            mTabPagerAdapterSearchMode = searchMode;
623            notifyDataSetChanged();
624        }
625
626        @Override
627        public int getCount() {
628            return mTabPagerAdapterSearchMode ? 1 : TabState.COUNT;
629        }
630
631        /** Gets called when the number of items changes. */
632        @Override
633        public int getItemPosition(Object object) {
634            if (mTabPagerAdapterSearchMode) {
635                if (object == mAllFragment) {
636                    return 0; // Only 1 page in search mode
637                }
638            } else {
639                if (object == mFavoritesFragment) {
640                    return TabState.FAVORITES;
641                }
642                if (object == mAllFragment) {
643                    return TabState.ALL;
644                }
645            }
646            return POSITION_NONE;
647        }
648
649        @Override
650        public void startUpdate(ViewGroup container) {
651        }
652
653        private Fragment getFragment(int position) {
654            if (mTabPagerAdapterSearchMode) {
655                if (position != 0) {
656                    // This has only been observed in monkey tests.
657                    // Let's log this issue, but not crash
658                    Log.w(TAG, "Request fragment at position=" + position + ", eventhough we " +
659                            "are in search mode");
660                }
661                return mAllFragment;
662            } else {
663                if (position == TabState.FAVORITES) {
664                    return mFavoritesFragment;
665                } else if (position == TabState.ALL) {
666                    return mAllFragment;
667                }
668            }
669            throw new IllegalArgumentException("position: " + position);
670        }
671
672        @Override
673        public Object instantiateItem(ViewGroup container, int position) {
674            if (mCurTransaction == null) {
675                mCurTransaction = mFragmentManager.beginTransaction();
676            }
677            Fragment f = getFragment(position);
678            mCurTransaction.show(f);
679
680            // Non primary pages are not visible.
681            f.setUserVisibleHint(f == mCurrentPrimaryItem);
682            return f;
683        }
684
685        @Override
686        public void destroyItem(ViewGroup container, int position, Object object) {
687            if (mCurTransaction == null) {
688                mCurTransaction = mFragmentManager.beginTransaction();
689            }
690            mCurTransaction.hide((Fragment) object);
691        }
692
693        @Override
694        public void finishUpdate(ViewGroup container) {
695            if (mCurTransaction != null) {
696                mCurTransaction.commitAllowingStateLoss();
697                mCurTransaction = null;
698                mFragmentManager.executePendingTransactions();
699            }
700        }
701
702        @Override
703        public boolean isViewFromObject(View view, Object object) {
704            return ((Fragment) object).getView() == view;
705        }
706
707        @Override
708        public void setPrimaryItem(ViewGroup container, int position, Object object) {
709            Fragment fragment = (Fragment) object;
710            if (mCurrentPrimaryItem != fragment) {
711                if (mCurrentPrimaryItem != null) {
712                    mCurrentPrimaryItem.setUserVisibleHint(false);
713                }
714                if (fragment != null) {
715                    fragment.setUserVisibleHint(true);
716                }
717                mCurrentPrimaryItem = fragment;
718            }
719        }
720
721        @Override
722        public Parcelable saveState() {
723            return null;
724        }
725
726        @Override
727        public void restoreState(Parcelable state, ClassLoader loader) {
728        }
729    }
730
731    private void setQueryTextToFragment(String query) {
732        mAllFragment.setQueryString(query, true);
733        mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode());
734    }
735
736    private void configureContactListFragmentForRequest() {
737        Uri contactUri = mRequest.getContactUri();
738        if (contactUri != null) {
739            mAllFragment.setSelectedContactUri(contactUri);
740        }
741
742        mAllFragment.setFilter(mContactListFilterController.getFilter());
743        setQueryTextToFragment(mActionBarAdapter.getQueryString());
744
745        if (mRequest.isDirectorySearchEnabled()) {
746            mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
747        } else {
748            mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
749        }
750    }
751
752    private void configureContactListFragment() {
753        // Filter may be changed when this Activity is in background.
754        mAllFragment.setFilter(mContactListFilterController.getFilter());
755
756        mAllFragment.setVerticalScrollbarPosition(getScrollBarPosition());
757        mAllFragment.setSelectionVisible(false);
758    }
759
760    private int getScrollBarPosition() {
761        return isRTL() ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
762    }
763
764    private boolean isRTL() {
765        final Locale locale = Locale.getDefault();
766        return TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
767    }
768
769    @Override
770    public void onProviderStatusChange() {
771        updateViewConfiguration(false);
772    }
773
774    private void updateViewConfiguration(boolean forceUpdate) {
775        ProviderStatusWatcher.Status providerStatus = mProviderStatusWatcher.getProviderStatus();
776        if (!forceUpdate && (mProviderStatus != null)
777                && (providerStatus.status == mProviderStatus.status)) return;
778        mProviderStatus = providerStatus;
779
780        View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view);
781
782        if (mProviderStatus.status == ProviderStatus.STATUS_NORMAL) {
783            // Ensure that the mTabPager is visible; we may have made it invisible below.
784            contactsUnavailableView.setVisibility(View.GONE);
785            if (mTabPager != null) {
786                mTabPager.setVisibility(View.VISIBLE);
787            }
788
789            if (mAllFragment != null) {
790                mAllFragment.setEnabled(true);
791            }
792        } else {
793            // If there are no accounts on the device and we should show the "no account" prompt
794            // (based on {@link SharedPreferences}), then launch the account setup activity so the
795            // user can sign-in or create an account.
796            //
797            // Also check for ability to modify accounts.  In limited user mode, you can't modify
798            // accounts so there is no point sending users to account setup activity.
799            final UserManager userManager = UserManager.get(this);
800            final boolean disallowModifyAccounts = userManager.getUserRestrictions().getBoolean(
801                    UserManager.DISALLOW_MODIFY_ACCOUNTS);
802            if (!disallowModifyAccounts && !areContactWritableAccountsAvailable() &&
803                    AccountPromptUtils.shouldShowAccountPrompt(this)) {
804                AccountPromptUtils.launchAccountPrompt(this);
805                return;
806            }
807
808            // Otherwise, continue setting up the page so that the user can still use the app
809            // without an account.
810            if (mAllFragment != null) {
811                mAllFragment.setEnabled(false);
812            }
813            if (mContactsUnavailableFragment == null) {
814                mContactsUnavailableFragment = new ContactsUnavailableFragment();
815                mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
816                        new ContactsUnavailableFragmentListener());
817                getFragmentManager().beginTransaction()
818                        .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment)
819                        .commitAllowingStateLoss();
820            }
821            mContactsUnavailableFragment.updateStatus(mProviderStatus);
822
823            // Show the contactsUnavailableView, and hide the mTabPager so that we don't
824            // see it sliding in underneath the contactsUnavailableView at the edges.
825            contactsUnavailableView.setVisibility(View.VISIBLE);
826            if (mTabPager != null) {
827                mTabPager.setVisibility(View.GONE);
828            }
829
830            showEmptyStateForTab(mActionBarAdapter.getCurrentTab());
831        }
832
833        invalidateOptionsMenuIfNeeded();
834    }
835
836    private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
837        ContactBrowserActionListener() {}
838
839        @Override
840        public void onSelectionChange() {
841
842        }
843
844        @Override
845        public void onViewContactAction(Uri contactLookupUri) {
846            Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this,
847                    (Rect) null, contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED, null);
848            startActivity(intent);
849        }
850
851        @Override
852        public void onDeleteContactAction(Uri contactUri) {
853            ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
854        }
855
856        @Override
857        public void onFinishAction() {
858            onBackPressed();
859        }
860
861        @Override
862        public void onInvalidSelection() {
863            ContactListFilter filter;
864            ContactListFilter currentFilter = mAllFragment.getFilter();
865            if (currentFilter != null
866                    && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
867                filter = ContactListFilter.createFilterWithType(
868                        ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
869                mAllFragment.setFilter(filter);
870            } else {
871                filter = ContactListFilter.createFilterWithType(
872                        ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
873                mAllFragment.setFilter(filter, false);
874            }
875            mContactListFilterController.setContactListFilter(filter, true);
876        }
877    }
878
879    private class ContactsUnavailableFragmentListener
880            implements OnContactsUnavailableActionListener {
881        ContactsUnavailableFragmentListener() {}
882
883        @Override
884        public void onCreateNewContactAction() {
885            startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
886        }
887
888        @Override
889        public void onAddAccountAction() {
890            Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
891            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
892            intent.putExtra(Settings.EXTRA_AUTHORITIES,
893                    new String[] { ContactsContract.AUTHORITY });
894            startActivity(intent);
895        }
896
897        @Override
898        public void onImportContactsFromFileAction() {
899            ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
900                    PeopleActivity.class);
901        }
902
903        @Override
904        public void onFreeInternalStorageAction() {
905            startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
906        }
907    }
908
909    private final class StrequentContactListFragmentListener
910            implements ContactTileListFragment.Listener {
911        StrequentContactListFragmentListener() {}
912
913        @Override
914        public void onContactSelected(Uri contactUri, Rect targetRect) {
915            Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this,
916                    targetRect, contactUri, QuickContactActivity.MODE_FULLY_EXPANDED, null);
917            startActivity(intent);
918        }
919
920        @Override
921        public void onCallNumberDirectly(String phoneNumber) {
922            // No need to call phone number directly from People app.
923            Log.w(TAG, "unexpected invocation of onCallNumberDirectly()");
924        }
925    }
926
927    @Override
928    public boolean onCreateOptionsMenu(Menu menu) {
929        if (!areContactsAvailable()) {
930            // If contacts aren't available, hide all menu items.
931            return false;
932        }
933        super.onCreateOptionsMenu(menu);
934
935        MenuInflater inflater = getMenuInflater();
936        inflater.inflate(R.menu.people_options, menu);
937
938        return true;
939    }
940
941    private void invalidateOptionsMenuIfNeeded() {
942        if (isOptionsMenuChanged()) {
943            invalidateOptionsMenu();
944        }
945    }
946
947    public boolean isOptionsMenuChanged() {
948        if (mOptionsMenuContactsAvailable != areContactsAvailable()) {
949            return true;
950        }
951
952        if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) {
953            return true;
954        }
955
956        return false;
957    }
958
959    @Override
960    public boolean onPrepareOptionsMenu(Menu menu) {
961        mOptionsMenuContactsAvailable = areContactsAvailable();
962        if (!mOptionsMenuContactsAvailable) {
963            return false;
964        }
965
966        // Get references to individual menu items in the menu
967        final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter);
968        final MenuItem clearFrequentsMenu = menu.findItem(R.id.menu_clear_frequents);
969        final MenuItem helpMenu = menu.findItem(R.id.menu_help);
970
971        final boolean isSearchMode = mActionBarAdapter.isSearchMode();
972        if (isSearchMode) {
973            contactsFilterMenu.setVisible(false);
974            clearFrequentsMenu.setVisible(false);
975            helpMenu.setVisible(false);
976        } else {
977            switch (mActionBarAdapter.getCurrentTab()) {
978                case TabState.FAVORITES:
979                    contactsFilterMenu.setVisible(false);
980                    clearFrequentsMenu.setVisible(hasFrequents());
981                    break;
982                case TabState.ALL:
983                    contactsFilterMenu.setVisible(true);
984                    clearFrequentsMenu.setVisible(false);
985                    break;
986            }
987            HelpUtils.prepareHelpMenuItem(this, helpMenu, R.string.help_url_people_main);
988        }
989        final boolean showMiscOptions = !isSearchMode;
990        makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions);
991        makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions);
992        makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions);
993        makeMenuItemVisible(menu, R.id.menu_settings,
994                showMiscOptions && !ContactsPreferenceActivity.isEmpty(this));
995
996        // Debug options need to be visible even in search mode.
997        makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions);
998
999        return true;
1000    }
1001
1002    /**
1003     * Returns whether there are any frequently contacted people being displayed
1004     * @return
1005     */
1006    private boolean hasFrequents() {
1007        return mFavoritesFragment.hasFrequents();
1008    }
1009
1010    private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
1011        MenuItem item =menu.findItem(itemId);
1012        if (item != null) {
1013            item.setVisible(visible);
1014        }
1015    }
1016
1017    @Override
1018    public boolean onOptionsItemSelected(MenuItem item) {
1019        if (mDisableOptionItemSelected) {
1020            return false;
1021        }
1022
1023        switch (item.getItemId()) {
1024            case android.R.id.home: {
1025                // The home icon on the action bar is pressed
1026                if (mActionBarAdapter.isUpShowing()) {
1027                    // "UP" icon press -- should be treated as "back".
1028                    onBackPressed();
1029                }
1030                return true;
1031            }
1032            case R.id.menu_settings: {
1033                final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
1034                // Since there is only one section right now, make sure it is selected on
1035                // small screens.
1036                intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
1037                        DisplayOptionsPreferenceFragment.class.getName());
1038                // By default, the title of the activity should be equivalent to the fragment
1039                // title. We set this argument to avoid this. Because of a bug, the following
1040                // line isn't necessary. But, once the bug is fixed this may become necessary.
1041                // b/5045558 refers to this issue, as well as another.
1042                intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE,
1043                        R.string.activity_title_settings);
1044                startActivity(intent);
1045                return true;
1046            }
1047            case R.id.menu_contacts_filter: {
1048                AccountFilterUtil.startAccountFilterActivityForResult(
1049                        this, SUBACTIVITY_ACCOUNT_FILTER,
1050                        mContactListFilterController.getFilter());
1051                return true;
1052            }
1053            case R.id.menu_search: {
1054                onSearchRequested();
1055                return true;
1056            }
1057            case R.id.menu_import_export: {
1058                ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
1059                        PeopleActivity.class);
1060                return true;
1061            }
1062            case R.id.menu_clear_frequents: {
1063                ClearFrequentsDialog.show(getFragmentManager());
1064                return true;
1065            }
1066            case R.id.menu_accounts: {
1067                final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
1068                intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
1069                    ContactsContract.AUTHORITY
1070                });
1071                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1072                startActivity(intent);
1073                return true;
1074            }
1075            case R.id.export_database: {
1076                final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
1077                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1078                startActivity(intent);
1079                return true;
1080            }
1081        }
1082        return false;
1083    }
1084
1085    @Override
1086    public boolean onSearchRequested() { // Search key pressed.
1087        mActionBarAdapter.setSearchMode(true);
1088        return true;
1089    }
1090
1091    @Override
1092    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1093        switch (requestCode) {
1094            case SUBACTIVITY_ACCOUNT_FILTER: {
1095                AccountFilterUtil.handleAccountFilterResult(
1096                        mContactListFilterController, resultCode, data);
1097                break;
1098            }
1099
1100            // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
1101            // anymore
1102            case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
1103                if (resultCode == RESULT_OK) {
1104                    mAllFragment.onPickerResult(data);
1105                }
1106
1107// TODO fix or remove multipicker code
1108//                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
1109//                    // Finish the activity if the sub activity was canceled as back key is used
1110//                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
1111//                    finish();
1112//                }
1113//                break;
1114        }
1115    }
1116
1117    @Override
1118    public boolean onKeyDown(int keyCode, KeyEvent event) {
1119        // TODO move to the fragment
1120        switch (keyCode) {
1121//            case KeyEvent.KEYCODE_CALL: {
1122//                if (callSelection()) {
1123//                    return true;
1124//                }
1125//                break;
1126//            }
1127
1128            case KeyEvent.KEYCODE_DEL: {
1129                if (deleteSelection()) {
1130                    return true;
1131                }
1132                break;
1133            }
1134            default: {
1135                // Bring up the search UI if the user starts typing
1136                final int unicodeChar = event.getUnicodeChar();
1137                if ((unicodeChar != 0)
1138                        // If COMBINING_ACCENT is set, it's not a unicode character.
1139                        && ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0)
1140                        && !Character.isWhitespace(unicodeChar)) {
1141                    String query = new String(new int[]{ unicodeChar }, 0, 1);
1142                    if (!mActionBarAdapter.isSearchMode()) {
1143                        mActionBarAdapter.setQueryString(query);
1144                        mActionBarAdapter.setSearchMode(true);
1145                        return true;
1146                    }
1147                }
1148            }
1149        }
1150
1151        return super.onKeyDown(keyCode, event);
1152    }
1153
1154    @Override
1155    public void onBackPressed() {
1156        if (mActionBarAdapter.isSearchMode()) {
1157            mActionBarAdapter.setSearchMode(false);
1158        } else {
1159            super.onBackPressed();
1160        }
1161    }
1162
1163    private boolean deleteSelection() {
1164        // TODO move to the fragment
1165//        if (mActionCode == ContactsRequest.ACTION_DEFAULT) {
1166//            final int position = mListView.getSelectedItemPosition();
1167//            if (position != ListView.INVALID_POSITION) {
1168//                Uri contactUri = getContactUri(position);
1169//                if (contactUri != null) {
1170//                    doContactDelete(contactUri);
1171//                    return true;
1172//                }
1173//            }
1174//        }
1175        return false;
1176    }
1177
1178    @Override
1179    protected void onSaveInstanceState(Bundle outState) {
1180        super.onSaveInstanceState(outState);
1181        mActionBarAdapter.onSaveInstanceState(outState);
1182
1183        // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
1184        // in order to avoid doing fragment transactions after it.
1185        // TODO Figure out a better way to deal with the issue.
1186        mDisableOptionItemSelected = true;
1187        mActionBarAdapter.setListener(null);
1188        if (mTabPager != null) {
1189            mTabPager.setOnPageChangeListener(null);
1190        }
1191    }
1192
1193    @Override
1194    protected void onRestoreInstanceState(Bundle savedInstanceState) {
1195        super.onRestoreInstanceState(savedInstanceState);
1196        // In our own lifecycle, the focus is saved and restore but later taken away by the
1197        // ViewPager. As a hack, we force focus on the SearchView if we know that we are searching.
1198        // This fixes the keyboard going away on screen rotation
1199        if (mActionBarAdapter.isSearchMode()) {
1200            mActionBarAdapter.setFocusOnSearchView();
1201        }
1202    }
1203
1204    @Override
1205    public DialogManager getDialogManager() {
1206        return mDialogManager;
1207    }
1208
1209    @Override
1210    public void onClick(View view) {
1211        switch (view.getId()) {
1212            case R.id.floating_action_button:
1213                Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
1214                Bundle extras = getIntent().getExtras();
1215                if (extras != null) {
1216                    intent.putExtras(extras);
1217                }
1218                startActivity(intent);
1219                break;
1220        default:
1221            Log.wtf(TAG, "Unexpected onClick event from " + view);
1222        }
1223    }
1224}
1225