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