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