PeopleActivity.java revision 8a6f4ade05e5a8a89d91078ef9c22944450ac8ba
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    }
223
224    @Override
225    protected void onNewIntent(Intent intent) {
226        setIntent(intent);
227        if (!processIntent(true)) {
228            finish();
229            return;
230        }
231        mActionBarAdapter.initialize(null, mRequest);
232
233        mContactListFilterController.checkFilterValidity(false);
234
235        // Re-configure fragments.
236        configureFragments(true /* from request */);
237        invalidateOptionsMenuIfNeeded();
238    }
239
240    /**
241     * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect
242     * is needed.
243     *
244     * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}.
245     * @return {@code true} if {@link PeopleActivity} should continue running.  {@code false}
246     *         if it shouldn't, in which case the caller should finish() itself and shouldn't do
247     *         farther initialization.
248     */
249    private boolean processIntent(boolean forNewIntent) {
250        // Extract relevant information from the intent
251        mRequest = mIntentResolver.resolveIntent(getIntent());
252        if (Log.isLoggable(TAG, Log.DEBUG)) {
253            Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent
254                    + " intent=" + getIntent() + " request=" + mRequest);
255        }
256        if (!mRequest.isValid()) {
257            setResult(RESULT_CANCELED);
258            return false;
259        }
260
261        Intent redirect = mRequest.getRedirectIntent();
262        if (redirect != null) {
263            // Need to start a different activity
264            startActivity(redirect);
265            return false;
266        }
267
268        if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT) {
269            redirect = new Intent(this, ContactDetailActivity.class);
270            redirect.setAction(Intent.ACTION_VIEW);
271            redirect.setData(mRequest.getContactUri());
272            startActivity(redirect);
273            return false;
274        }
275        return true;
276    }
277
278    private void createViewsAndFragments(Bundle savedState) {
279        setContentView(R.layout.people_activity);
280
281        final FragmentManager fragmentManager = getFragmentManager();
282
283        // Hide all tabs (the current tab will later be reshown once a tab is selected)
284        final FragmentTransaction transaction = fragmentManager.beginTransaction();
285
286        mTabPager = getView(R.id.tab_pager);
287        mTabPagerAdapter = new TabPagerAdapter();
288        mTabPager.setAdapter(mTabPagerAdapter);
289        mTabPager.setOnPageChangeListener(mTabPagerListener);
290
291        final String FAVORITE_TAG = "tab-pager-favorite";
292        final String ALL_TAG = "tab-pager-all";
293
294        // Create the fragments and add as children of the view pager.
295        // The pager adapter will only change the visibility; it'll never create/destroy
296        // fragments.
297        // However, if it's after screen rotation, the fragments have been re-created by
298        // the fragment manager, so first see if there're already the target fragments
299        // existing.
300        mFavoritesFragment = (ContactTileListFragment)
301                fragmentManager.findFragmentByTag(FAVORITE_TAG);
302        mAllFragment = (DefaultContactBrowseListFragment)
303                fragmentManager.findFragmentByTag(ALL_TAG);
304
305        if (mFavoritesFragment == null) {
306            mFavoritesFragment = new ContactTileListFragment();
307            mAllFragment = new DefaultContactBrowseListFragment();
308
309            transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG);
310            transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG);
311        }
312
313        mFavoritesFragment.setListener(mFavoritesFragmentListener);
314
315        mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener());
316
317        // Hide all fragments for now.  We adjust visibility when we get onSelectedTabChanged()
318        // from ActionBarAdapter.
319        transaction.hide(mFavoritesFragment);
320        transaction.hide(mAllFragment);
321
322        transaction.commitAllowingStateLoss();
323        fragmentManager.executePendingTransactions();
324
325        // Setting Properties after fragment is created
326        mFavoritesFragment.setDisplayType(DisplayType.STREQUENT);
327
328        // Configure action bar
329        mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar());
330        mActionBarAdapter.initialize(savedState, mRequest);
331
332        // Configure action button
333        final View floatingActionButtonContainer = findViewById(
334                R.id.floating_action_button_container);
335        ViewUtil.setupFloatingActionButton(floatingActionButtonContainer, getResources());
336        final ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
337        floatingActionButton.setOnClickListener(this);
338
339        invalidateOptionsMenuIfNeeded();
340    }
341
342    @Override
343    protected void onStart() {
344        if (!mFragmentInitialized) {
345            mFragmentInitialized = true;
346            /* Configure fragments if we haven't.
347             *
348             * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}.
349             *
350             * However, because this method may indirectly touch views in fragments but fragments
351             * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT
352             * have views until {@link Activity#onCreate} finishes (they would if they were inflated
353             * from a layout), we need to do it here in {@link #onStart()}.
354             *
355             * (When {@link Fragment#onCreateView} is called is different in the former case and
356             * in the latter case, unfortunately.)
357             *
358             * Also, we skip most of the work in it if the activity is a re-created one.
359             * (so the argument.)
360             */
361            configureFragments(!mIsRecreatedInstance);
362        }
363        super.onStart();
364    }
365
366    @Override
367    protected void onPause() {
368        mOptionsMenuContactsAvailable = false;
369        mProviderStatusWatcher.stop();
370        super.onPause();
371    }
372
373    @Override
374    protected void onResume() {
375        super.onResume();
376
377        mProviderStatusWatcher.start();
378        updateViewConfiguration(true);
379
380        // Re-register the listener, which may have been cleared when onSaveInstanceState was
381        // called.  See also: onSaveInstanceState
382        mActionBarAdapter.setListener(this);
383        mDisableOptionItemSelected = false;
384        if (mTabPager != null) {
385            mTabPager.setOnPageChangeListener(mTabPagerListener);
386        }
387        // Current tab may have changed since the last onSaveInstanceState().  Make sure
388        // the actual contents match the tab.
389        updateFragmentsVisibility();
390    }
391
392    @Override
393    protected void onStop() {
394        super.onStop();
395    }
396
397    @Override
398    protected void onDestroy() {
399        mProviderStatusWatcher.removeListener(this);
400
401        // Some of variables will be null if this Activity redirects Intent.
402        // See also onCreate() or other methods called during the Activity's initialization.
403        if (mActionBarAdapter != null) {
404            mActionBarAdapter.setListener(null);
405        }
406        if (mContactListFilterController != null) {
407            mContactListFilterController.removeListener(this);
408        }
409
410        super.onDestroy();
411    }
412
413    private void configureFragments(boolean fromRequest) {
414        if (fromRequest) {
415            ContactListFilter filter = null;
416            int actionCode = mRequest.getActionCode();
417            boolean searchMode = mRequest.isSearchMode();
418            final int tabToOpen;
419            switch (actionCode) {
420                case ContactsRequest.ACTION_ALL_CONTACTS:
421                    filter = ContactListFilter.createFilterWithType(
422                            ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
423                    tabToOpen = TabState.ALL;
424                    break;
425                case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
426                    filter = ContactListFilter.createFilterWithType(
427                            ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
428                    tabToOpen = TabState.ALL;
429                    break;
430
431                case ContactsRequest.ACTION_FREQUENT:
432                case ContactsRequest.ACTION_STREQUENT:
433                case ContactsRequest.ACTION_STARRED:
434                    tabToOpen = TabState.FAVORITES;
435                    break;
436                case ContactsRequest.ACTION_VIEW_CONTACT:
437                    tabToOpen = TabState.ALL;
438                    break;
439                default:
440                    tabToOpen = -1;
441                    break;
442            }
443            if (tabToOpen != -1) {
444                mActionBarAdapter.setCurrentTab(tabToOpen);
445            }
446
447            if (filter != null) {
448                mContactListFilterController.setContactListFilter(filter, false);
449                searchMode = false;
450            }
451
452            if (mRequest.getContactUri() != null) {
453                searchMode = false;
454            }
455
456            mActionBarAdapter.setSearchMode(searchMode);
457            configureContactListFragmentForRequest();
458        }
459
460        configureContactListFragment();
461
462        invalidateOptionsMenuIfNeeded();
463    }
464
465    @Override
466    public void onContactListFilterChanged() {
467        if (mAllFragment == null || !mAllFragment.isAdded()) {
468            return;
469        }
470
471        mAllFragment.setFilter(mContactListFilterController.getFilter());
472
473        invalidateOptionsMenuIfNeeded();
474    }
475
476    /**
477     * Handler for action bar actions.
478     */
479    @Override
480    public void onAction(int action) {
481        switch (action) {
482            case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
483                // Tell the fragments that we're in the search mode
484                configureFragments(false /* from request */);
485                updateFragmentsVisibility();
486                invalidateOptionsMenu();
487                break;
488            case ActionBarAdapter.Listener.Action.STOP_SEARCH_MODE:
489                setQueryTextToFragment("");
490                updateFragmentsVisibility();
491                invalidateOptionsMenu();
492                break;
493            case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
494                final String queryString = mActionBarAdapter.getQueryString();
495                setQueryTextToFragment(queryString);
496                updateDebugOptionsVisibility(
497                        ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
498                break;
499            default:
500                throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action);
501        }
502    }
503
504    @Override
505    public void onSelectedTabChanged() {
506        updateFragmentsVisibility();
507    }
508
509    @Override
510    public void onUpButtonPressed() {
511        onBackPressed();
512    }
513
514    private void updateDebugOptionsVisibility(boolean visible) {
515        if (mEnableDebugMenuOptions != visible) {
516            mEnableDebugMenuOptions = visible;
517            invalidateOptionsMenu();
518        }
519    }
520
521    /**
522     * Updates the fragment/view visibility according to the current mode, such as
523     * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}.
524     */
525    private void updateFragmentsVisibility() {
526        int tab = mActionBarAdapter.getCurrentTab();
527
528        if (mActionBarAdapter.isSearchMode()) {
529            mTabPagerAdapter.setSearchMode(true);
530        } else {
531            // No smooth scrolling if quitting from the search mode.
532            final boolean wasSearchMode = mTabPagerAdapter.isSearchMode();
533            mTabPagerAdapter.setSearchMode(false);
534            if (mTabPager.getCurrentItem() != tab) {
535                mTabPager.setCurrentItem(tab, !wasSearchMode);
536            }
537        }
538        invalidateOptionsMenu();
539        showEmptyStateForTab(tab);
540    }
541
542    private void showEmptyStateForTab(int tab) {
543        if (mContactsUnavailableFragment != null) {
544            switch (tab) {
545                case TabState.FAVORITES:
546                    mContactsUnavailableFragment.setMessageText(
547                            R.string.listTotalAllContactsZeroStarred, -1);
548                    break;
549                case TabState.ALL:
550                    mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1);
551                    break;
552            }
553        }
554    }
555
556    private class TabPagerListener implements ViewPager.OnPageChangeListener {
557
558        // This package-protected constructor is here because of a possible compiler bug.
559        // PeopleActivity$1.class should be generated due to the private outer/inner class access
560        // needed here.  But for some reason, PeopleActivity$1.class is missing.
561        // Since $1 class is needed as a jvm work around to get access to the inner class,
562        // changing the constructor to package-protected or public will solve the problem.
563        // To verify whether $1 class is needed, javap PeopleActivity$TabPagerListener and look for
564        // references to PeopleActivity$1.
565        //
566        // When the constructor is private and PeopleActivity$1.class is missing, proguard will
567        // correctly catch this and throw warnings and error out the build on user/userdebug builds.
568        //
569        // All private inner classes below also need this fix.
570        TabPagerListener() {}
571
572        @Override
573        public void onPageScrollStateChanged(int state) {
574        }
575
576        @Override
577        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
578        }
579
580        @Override
581        public void onPageSelected(int position) {
582            // Make sure not in the search mode, in which case position != TabState.ordinal().
583            if (!mTabPagerAdapter.isSearchMode()) {
584                mActionBarAdapter.setCurrentTab(position, false);
585                showEmptyStateForTab(position);
586                invalidateOptionsMenu();
587            }
588        }
589    }
590
591    /**
592     * Adapter for the {@link ViewPager}.  Unlike {@link FragmentPagerAdapter},
593     * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/
594     * {@link #destroyItem} show/hide fragments instead of attaching/detaching.
595     *
596     * In search mode, we always show the "all" fragment, and disable the swipe.  We change the
597     * number of items to 1 to disable the swipe.
598     *
599     * TODO figure out a more straight way to disable swipe.
600     */
601    private class TabPagerAdapter extends PagerAdapter {
602        private final FragmentManager mFragmentManager;
603        private FragmentTransaction mCurTransaction = null;
604
605        private boolean mTabPagerAdapterSearchMode;
606
607        private Fragment mCurrentPrimaryItem;
608
609        public TabPagerAdapter() {
610            mFragmentManager = getFragmentManager();
611        }
612
613        public boolean isSearchMode() {
614            return mTabPagerAdapterSearchMode;
615        }
616
617        public void setSearchMode(boolean searchMode) {
618            if (searchMode == mTabPagerAdapterSearchMode) {
619                return;
620            }
621            mTabPagerAdapterSearchMode = searchMode;
622            notifyDataSetChanged();
623        }
624
625        @Override
626        public int getCount() {
627            return mTabPagerAdapterSearchMode ? 1 : TabState.COUNT;
628        }
629
630        /** Gets called when the number of items changes. */
631        @Override
632        public int getItemPosition(Object object) {
633            if (mTabPagerAdapterSearchMode) {
634                if (object == mAllFragment) {
635                    return 0; // Only 1 page in search mode
636                }
637            } else {
638                if (object == mFavoritesFragment) {
639                    return TabState.FAVORITES;
640                }
641                if (object == mAllFragment) {
642                    return TabState.ALL;
643                }
644            }
645            return POSITION_NONE;
646        }
647
648        @Override
649        public void startUpdate(ViewGroup container) {
650        }
651
652        private Fragment getFragment(int position) {
653            if (mTabPagerAdapterSearchMode) {
654                if (position != 0) {
655                    // This has only been observed in monkey tests.
656                    // Let's log this issue, but not crash
657                    Log.w(TAG, "Request fragment at position=" + position + ", eventhough we " +
658                            "are in search mode");
659                }
660                return mAllFragment;
661            } else {
662                if (position == TabState.FAVORITES) {
663                    return mFavoritesFragment;
664                } else if (position == TabState.ALL) {
665                    return mAllFragment;
666                }
667            }
668            throw new IllegalArgumentException("position: " + position);
669        }
670
671        @Override
672        public Object instantiateItem(ViewGroup container, int position) {
673            if (mCurTransaction == null) {
674                mCurTransaction = mFragmentManager.beginTransaction();
675            }
676            Fragment f = getFragment(position);
677            mCurTransaction.show(f);
678
679            // Non primary pages are not visible.
680            f.setUserVisibleHint(f == mCurrentPrimaryItem);
681            return f;
682        }
683
684        @Override
685        public void destroyItem(ViewGroup container, int position, Object object) {
686            if (mCurTransaction == null) {
687                mCurTransaction = mFragmentManager.beginTransaction();
688            }
689            mCurTransaction.hide((Fragment) object);
690        }
691
692        @Override
693        public void finishUpdate(ViewGroup container) {
694            if (mCurTransaction != null) {
695                mCurTransaction.commitAllowingStateLoss();
696                mCurTransaction = null;
697                mFragmentManager.executePendingTransactions();
698            }
699        }
700
701        @Override
702        public boolean isViewFromObject(View view, Object object) {
703            return ((Fragment) object).getView() == view;
704        }
705
706        @Override
707        public void setPrimaryItem(ViewGroup container, int position, Object object) {
708            Fragment fragment = (Fragment) object;
709            if (mCurrentPrimaryItem != fragment) {
710                if (mCurrentPrimaryItem != null) {
711                    mCurrentPrimaryItem.setUserVisibleHint(false);
712                }
713                if (fragment != null) {
714                    fragment.setUserVisibleHint(true);
715                }
716                mCurrentPrimaryItem = fragment;
717            }
718        }
719
720        @Override
721        public Parcelable saveState() {
722            return null;
723        }
724
725        @Override
726        public void restoreState(Parcelable state, ClassLoader loader) {
727        }
728    }
729
730    private void setQueryTextToFragment(String query) {
731        mAllFragment.setQueryString(query, true);
732        mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode());
733    }
734
735    private void configureContactListFragmentForRequest() {
736        Uri contactUri = mRequest.getContactUri();
737        if (contactUri != null) {
738            mAllFragment.setSelectedContactUri(contactUri);
739        }
740
741        mAllFragment.setFilter(mContactListFilterController.getFilter());
742        setQueryTextToFragment(mActionBarAdapter.getQueryString());
743
744        if (mRequest.isDirectorySearchEnabled()) {
745            mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
746        } else {
747            mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
748        }
749    }
750
751    private void configureContactListFragment() {
752        // Filter may be changed when this Activity is in background.
753        mAllFragment.setFilter(mContactListFilterController.getFilter());
754
755        mAllFragment.setVerticalScrollbarPosition(getScrollBarPosition());
756        mAllFragment.setSelectionVisible(false);
757    }
758
759    private int getScrollBarPosition() {
760        return isRTL() ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
761    }
762
763    private boolean isRTL() {
764        final Locale locale = Locale.getDefault();
765        return TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
766    }
767
768    @Override
769    public void onProviderStatusChange() {
770        updateViewConfiguration(false);
771    }
772
773    private void updateViewConfiguration(boolean forceUpdate) {
774        ProviderStatusWatcher.Status providerStatus = mProviderStatusWatcher.getProviderStatus();
775        if (!forceUpdate && (mProviderStatus != null)
776                && (providerStatus.status == mProviderStatus.status)) return;
777        mProviderStatus = providerStatus;
778
779        View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view);
780
781        if (mProviderStatus.status == ProviderStatus.STATUS_NORMAL) {
782            // Ensure that the mTabPager is visible; we may have made it invisible below.
783            contactsUnavailableView.setVisibility(View.GONE);
784            if (mTabPager != null) {
785                mTabPager.setVisibility(View.VISIBLE);
786            }
787
788            if (mAllFragment != null) {
789                mAllFragment.setEnabled(true);
790            }
791        } else {
792            // If there are no accounts on the device and we should show the "no account" prompt
793            // (based on {@link SharedPreferences}), then launch the account setup activity so the
794            // user can sign-in or create an account.
795            //
796            // Also check for ability to modify accounts.  In limited user mode, you can't modify
797            // accounts so there is no point sending users to account setup activity.
798            final UserManager userManager = UserManager.get(this);
799            final boolean disallowModifyAccounts = userManager.getUserRestrictions().getBoolean(
800                    UserManager.DISALLOW_MODIFY_ACCOUNTS);
801            if (!disallowModifyAccounts && !areContactWritableAccountsAvailable() &&
802                    AccountPromptUtils.shouldShowAccountPrompt(this)) {
803                AccountPromptUtils.launchAccountPrompt(this);
804                return;
805            }
806
807            // Otherwise, continue setting up the page so that the user can still use the app
808            // without an account.
809            if (mAllFragment != null) {
810                mAllFragment.setEnabled(false);
811            }
812            if (mContactsUnavailableFragment == null) {
813                mContactsUnavailableFragment = new ContactsUnavailableFragment();
814                mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
815                        new ContactsUnavailableFragmentListener());
816                getFragmentManager().beginTransaction()
817                        .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment)
818                        .commitAllowingStateLoss();
819            }
820            mContactsUnavailableFragment.updateStatus(mProviderStatus);
821
822            // Show the contactsUnavailableView, and hide the mTabPager so that we don't
823            // see it sliding in underneath the contactsUnavailableView at the edges.
824            contactsUnavailableView.setVisibility(View.VISIBLE);
825            if (mTabPager != null) {
826                mTabPager.setVisibility(View.GONE);
827            }
828
829            showEmptyStateForTab(mActionBarAdapter.getCurrentTab());
830        }
831
832        invalidateOptionsMenuIfNeeded();
833    }
834
835    private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
836        ContactBrowserActionListener() {}
837
838        @Override
839        public void onSelectionChange() {
840
841        }
842
843        @Override
844        public void onViewContactAction(Uri contactLookupUri) {
845            Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this,
846                    getCurrentFocus().getRootView(), contactLookupUri,
847                    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 addContactMenu = menu.findItem(R.id.menu_add_contact);
968        final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter);
969
970        final MenuItem clearFrequentsMenu = menu.findItem(R.id.menu_clear_frequents);
971        final MenuItem helpMenu = menu.findItem(R.id.menu_help);
972
973        final boolean isSearchMode = mActionBarAdapter.isSearchMode();
974        if (isSearchMode) {
975            addContactMenu.setVisible(false);
976            contactsFilterMenu.setVisible(false);
977            clearFrequentsMenu.setVisible(false);
978            helpMenu.setVisible(false);
979        } else {
980            switch (mActionBarAdapter.getCurrentTab()) {
981                case TabState.FAVORITES:
982                    addContactMenu.setVisible(true);
983                    contactsFilterMenu.setVisible(false);
984                    clearFrequentsMenu.setVisible(hasFrequents());
985                    break;
986                case TabState.ALL:
987                    addContactMenu.setVisible(true);
988                    contactsFilterMenu.setVisible(true);
989                    clearFrequentsMenu.setVisible(false);
990                    break;
991            }
992            HelpUtils.prepareHelpMenuItem(this, helpMenu, R.string.help_url_people_main);
993        }
994        final boolean showMiscOptions = !isSearchMode;
995        makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions);
996        makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions);
997        makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions);
998        makeMenuItemVisible(menu, R.id.menu_settings,
999                showMiscOptions && !ContactsPreferenceActivity.isEmpty(this));
1000
1001        // Debug options need to be visible even in search mode.
1002        makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions);
1003
1004        return true;
1005    }
1006
1007    /**
1008     * Returns whether there are any frequently contacted people being displayed
1009     * @return
1010     */
1011    private boolean hasFrequents() {
1012        return mFavoritesFragment.hasFrequents();
1013    }
1014
1015    private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
1016        MenuItem item =menu.findItem(itemId);
1017        if (item != null) {
1018            item.setVisible(visible);
1019        }
1020    }
1021
1022    @Override
1023    public boolean onOptionsItemSelected(MenuItem item) {
1024        if (mDisableOptionItemSelected) {
1025            return false;
1026        }
1027
1028        switch (item.getItemId()) {
1029            case android.R.id.home: {
1030                // The home icon on the action bar is pressed
1031                if (mActionBarAdapter.isUpShowing()) {
1032                    // "UP" icon press -- should be treated as "back".
1033                    onBackPressed();
1034                }
1035                return true;
1036            }
1037            case R.id.menu_settings: {
1038                final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
1039                // as there is only one section right now, make sure it is selected
1040                // on small screens, this also hides the section selector
1041                // Due to b/5045558, this code unfortunately only works properly on phones
1042                boolean settingsAreMultiPane = getResources().getBoolean(
1043                        com.android.internal.R.bool.preferences_prefer_dual_pane);
1044                if (!settingsAreMultiPane) {
1045                    intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
1046                            DisplayOptionsPreferenceFragment.class.getName());
1047                    intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE,
1048                            R.string.activity_title_settings);
1049                }
1050                startActivity(intent);
1051                return true;
1052            }
1053            case R.id.menu_contacts_filter: {
1054                AccountFilterUtil.startAccountFilterActivityForResult(
1055                        this, SUBACTIVITY_ACCOUNT_FILTER,
1056                        mContactListFilterController.getFilter());
1057                return true;
1058            }
1059            case R.id.menu_search: {
1060                onSearchRequested();
1061                return true;
1062            }
1063            case R.id.menu_add_contact: {
1064                final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
1065                startActivity(intent);
1066                return true;
1067            }
1068            case R.id.menu_import_export: {
1069                ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
1070                        PeopleActivity.class);
1071                return true;
1072            }
1073            case R.id.menu_clear_frequents: {
1074                ClearFrequentsDialog.show(getFragmentManager());
1075                return true;
1076            }
1077            case R.id.menu_accounts: {
1078                final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
1079                intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
1080                    ContactsContract.AUTHORITY
1081                });
1082                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1083                startActivity(intent);
1084                return true;
1085            }
1086            case R.id.export_database: {
1087                final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
1088                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1089                startActivity(intent);
1090                return true;
1091            }
1092        }
1093        return false;
1094    }
1095
1096    @Override
1097    public boolean onSearchRequested() { // Search key pressed.
1098        mActionBarAdapter.setSearchMode(true);
1099        return true;
1100    }
1101
1102    @Override
1103    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1104        switch (requestCode) {
1105            case SUBACTIVITY_ACCOUNT_FILTER: {
1106                AccountFilterUtil.handleAccountFilterResult(
1107                        mContactListFilterController, resultCode, data);
1108                break;
1109            }
1110
1111            // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
1112            // anymore
1113            case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
1114                if (resultCode == RESULT_OK) {
1115                    mAllFragment.onPickerResult(data);
1116                }
1117
1118// TODO fix or remove multipicker code
1119//                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
1120//                    // Finish the activity if the sub activity was canceled as back key is used
1121//                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
1122//                    finish();
1123//                }
1124//                break;
1125        }
1126    }
1127
1128    @Override
1129    public boolean onKeyDown(int keyCode, KeyEvent event) {
1130        // TODO move to the fragment
1131        switch (keyCode) {
1132//            case KeyEvent.KEYCODE_CALL: {
1133//                if (callSelection()) {
1134//                    return true;
1135//                }
1136//                break;
1137//            }
1138
1139            case KeyEvent.KEYCODE_DEL: {
1140                if (deleteSelection()) {
1141                    return true;
1142                }
1143                break;
1144            }
1145            default: {
1146                // Bring up the search UI if the user starts typing
1147                final int unicodeChar = event.getUnicodeChar();
1148                if ((unicodeChar != 0)
1149                        // If COMBINING_ACCENT is set, it's not a unicode character.
1150                        && ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0)
1151                        && !Character.isWhitespace(unicodeChar)) {
1152                    String query = new String(new int[]{ unicodeChar }, 0, 1);
1153                    if (!mActionBarAdapter.isSearchMode()) {
1154                        mActionBarAdapter.setQueryString(query);
1155                        mActionBarAdapter.setSearchMode(true);
1156                        return true;
1157                    }
1158                }
1159            }
1160        }
1161
1162        return super.onKeyDown(keyCode, event);
1163    }
1164
1165    @Override
1166    public void onBackPressed() {
1167        if (mActionBarAdapter.isSearchMode()) {
1168            mActionBarAdapter.setSearchMode(false);
1169        } else {
1170            super.onBackPressed();
1171        }
1172    }
1173
1174    private boolean deleteSelection() {
1175        // TODO move to the fragment
1176//        if (mActionCode == ContactsRequest.ACTION_DEFAULT) {
1177//            final int position = mListView.getSelectedItemPosition();
1178//            if (position != ListView.INVALID_POSITION) {
1179//                Uri contactUri = getContactUri(position);
1180//                if (contactUri != null) {
1181//                    doContactDelete(contactUri);
1182//                    return true;
1183//                }
1184//            }
1185//        }
1186        return false;
1187    }
1188
1189    @Override
1190    protected void onSaveInstanceState(Bundle outState) {
1191        super.onSaveInstanceState(outState);
1192        mActionBarAdapter.onSaveInstanceState(outState);
1193
1194        // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
1195        // in order to avoid doing fragment transactions after it.
1196        // TODO Figure out a better way to deal with the issue.
1197        mDisableOptionItemSelected = true;
1198        mActionBarAdapter.setListener(null);
1199        if (mTabPager != null) {
1200            mTabPager.setOnPageChangeListener(null);
1201        }
1202    }
1203
1204    @Override
1205    protected void onRestoreInstanceState(Bundle savedInstanceState) {
1206        super.onRestoreInstanceState(savedInstanceState);
1207        // In our own lifecycle, the focus is saved and restore but later taken away by the
1208        // ViewPager. As a hack, we force focus on the SearchView if we know that we are searching.
1209        // This fixes the keyboard going away on screen rotation
1210        if (mActionBarAdapter.isSearchMode()) {
1211            mActionBarAdapter.setFocusOnSearchView();
1212        }
1213    }
1214
1215    @Override
1216    public DialogManager getDialogManager() {
1217        return mDialogManager;
1218    }
1219
1220    @Override
1221    public void onClick(View view) {
1222        switch (view.getId()) {
1223            case R.id.floating_action_button:
1224                Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
1225                Bundle extras = getIntent().getExtras();
1226                if (extras != null) {
1227                    intent.putExtras(extras);
1228                }
1229                startActivity(intent);
1230                break;
1231        default:
1232            Log.wtf(TAG, "Unexpected onClick event from " + view);
1233        }
1234    }
1235}
1236