com.android.contacts.list.DefaultContactBrowseListFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.android.contacts.list.DefaultContactBrowseListFragment.java

Source

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.contacts.list;

import android.accounts.Account;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Directory;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import android.widget.Toast;

import com.android.contacts.ContactSaveService;
import com.android.contacts.Experiments;
import com.android.contacts.R;
import com.android.contacts.activities.ActionBarAdapter;
import com.android.contacts.activities.PeopleActivity;
import com.android.contacts.compat.CompatUtils;
import com.android.contacts.interactions.ContactDeletionInteraction;
import com.android.contacts.interactions.ContactMultiDeletionInteraction;
import com.android.contacts.interactions.ContactMultiDeletionInteraction.MultiContactDeleteListener;
import com.android.contacts.logging.ListEvent;
import com.android.contacts.logging.Logger;
import com.android.contacts.logging.ScreenEvent;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.account.AccountInfo;
import com.android.contacts.model.account.AccountWithDataSet;
import com.android.contacts.quickcontact.QuickContactActivity;
import com.android.contacts.util.AccountFilterUtil;
import com.android.contacts.util.ImplicitIntentsUtil;
import com.android.contacts.util.SharedPreferenceUtil;
import com.android.contacts.util.SyncUtil;
import com.android.contactsbind.FeatureHighlightHelper;
import com.android.contactsbind.experiments.Flags;
import com.google.common.util.concurrent.Futures;

import java.util.List;
import java.util.Locale;
import java.util.concurrent.Future;

/**
 * Fragment containing a contact list used for browsing (as compared to
 * picking a contact with one of the PICK intents).
 */
public class DefaultContactBrowseListFragment extends ContactBrowseListFragment
        implements EnableGlobalSyncDialogFragment.Listener {

    private static final String TAG = "DefaultListFragment";
    private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
    private static final String KEY_DELETION_IN_PROGRESS = "deletionInProgress";
    private static final String KEY_SEARCH_RESULT_CLICKED = "search_result_clicked";

    private static final int ACTIVITY_REQUEST_CODE_SHARE = 0;

    private View mSearchHeaderView;
    private View mSearchProgress;
    private View mEmptyAccountView;
    private View mEmptyHomeView;
    private View mAccountFilterContainer;
    private TextView mSearchProgressText;

    private SwipeRefreshLayout mSwipeRefreshLayout;
    private final Handler mHandler = new Handler();
    private final Runnable mCancelRefresh = new Runnable() {
        @Override
        public void run() {
            if (mSwipeRefreshLayout.isRefreshing()) {
                mSwipeRefreshLayout.setRefreshing(false);
            }
        }
    };

    private View mAlertContainer;
    private TextView mAlertText;
    private ImageView mAlertDismissIcon;
    private int mReasonSyncOff = SyncUtil.SYNC_SETTING_SYNC_ON;

    private boolean mContactsAvailable;
    private boolean mEnableDebugMenuOptions;
    private boolean mIsRecreatedInstance;
    private boolean mOptionsMenuContactsAvailable;

    private boolean mCanSetActionBar = false;

    /**
     * If {@link #configureFragment()} is already called. Used to avoid calling it twice
     * in {@link #onResume()}.
     * (This initialization only needs to be done once in onResume() when the Activity was just
     * created from scratch -- i.e. onCreate() was just called)
     */
    private boolean mFragmentInitialized;

    private boolean mFromOnNewIntent;

    /**
     * This is to tell whether we need to restart ContactMultiDeletionInteraction and set listener.
     * if screen is rotated while deletion dialog is shown.
     */
    private boolean mIsDeletionInProgress;

    /**
     * This is to disable {@link #onOptionsItemSelected} when we trying to stop the
     * activity/fragment.
     */
    private boolean mDisableOptionItemSelected;

    private boolean mSearchResultClicked;

    private ActionBarAdapter mActionBarAdapter;
    private PeopleActivity mActivity;
    private ContactsRequest mContactsRequest;
    private ContactListFilterController mContactListFilterController;

    private Future<List<AccountInfo>> mWritableAccountsFuture;

    private final ActionBarAdapter.Listener mActionBarListener = new ActionBarAdapter.Listener() {
        @Override
        public void onAction(int action) {
            switch (action) {
            case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
                displayCheckBoxes(true);
                startSearchOrSelectionMode();
                break;
            case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
                if (!mIsRecreatedInstance) {
                    Logger.logScreenView(mActivity, ScreenEvent.ScreenType.SEARCH);
                }
                startSearchOrSelectionMode();
                break;
            case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE:
                mActivity.showFabWithAnimation(/* showFab */ true);
                break;
            case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
                // If queryString is empty, fragment data will not be reloaded,
                // so hamburger promo should be checked now.
                // Otherwise, promo should be checked and displayed after reloading, b/30706521.
                if (TextUtils.isEmpty(getQueryString())) {
                    maybeShowHamburgerFeatureHighlight();
                }
                setQueryTextToFragment("");
                maybeHideCheckBoxes();
                mActivity.invalidateOptionsMenu();
                mActivity.showFabWithAnimation(/* showFab */ true);

                // Alert user if sync is off and not dismissed before
                setSyncOffAlert();

                // Determine whether the account has pullToRefresh feature
                setSwipeRefreshLayoutEnabledOrNot(getFilter());
                break;
            case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
                final String queryString = mActionBarAdapter.getQueryString();
                setQueryTextToFragment(queryString);
                updateDebugOptionsVisibility(ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
                break;
            default:
                throw new IllegalStateException("Unknown ActionBarAdapter action: " + action);
            }
        }

        private void startSearchOrSelectionMode() {
            configureContactListFragment();
            maybeHideCheckBoxes();
            mActivity.invalidateOptionsMenu();
            mActivity.showFabWithAnimation(/* showFab */ false);

            final Context context = getContext();
            if (!SharedPreferenceUtil.getHamburgerPromoTriggerActionHappenedBefore(context)) {
                SharedPreferenceUtil.setHamburgerPromoTriggerActionHappenedBefore(context);
            }
        }

        private void updateDebugOptionsVisibility(boolean visible) {
            if (mEnableDebugMenuOptions != visible) {
                mEnableDebugMenuOptions = visible;
                mActivity.invalidateOptionsMenu();
            }
        }

        private void setQueryTextToFragment(String query) {
            setQueryString(query, true);
            setVisibleScrollbarEnabled(!isSearchMode());
        }

        @Override
        public void onUpButtonPressed() {
            mActivity.onBackPressed();
        }
    };

    private final View.OnClickListener mAddContactListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            AccountFilterUtil.startEditorIntent(getContext(), mActivity.getIntent(), getFilter());
        }
    };

    public DefaultContactBrowseListFragment() {
        setPhotoLoaderEnabled(true);
        // Don't use a QuickContactBadge. Just use a regular ImageView. Using a QuickContactBadge
        // inside the ListView prevents us from using MODE_FULLY_EXPANDED and messes up ripples.
        setQuickContactEnabled(false);
        setSectionHeaderDisplayEnabled(true);
        setVisibleScrollbarEnabled(true);
        setDisplayDirectoryHeader(false);
        setHasOptionsMenu(true);
    }

    /**
     * Whether a search result was clicked by the user. Tracked so that we can distinguish
     * between exiting the search mode after a result was clicked from exiting w/o clicking
     * any search result.
     */
    public boolean wasSearchResultClicked() {
        return mSearchResultClicked;
    }

    /**
     * Resets whether a search result was clicked by the user to false.
     */
    public void resetSearchResultClicked() {
        mSearchResultClicked = false;
    }

    @Override
    public CursorLoader createCursorLoader(Context context) {
        return new FavoritesAndContactsLoader(context);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        if (loader.getId() == Directory.DEFAULT) {
            bindListHeader(data == null ? 0 : data.getCount());
        }
        super.onLoadFinished(loader, data);
        if (!isSearchMode()) {
            maybeShowHamburgerFeatureHighlight();
        }
        if (mActionBarAdapter != null) {
            mActionBarAdapter.updateOverflowButtonColor();
        }
    }

    private void maybeShowHamburgerFeatureHighlight() {
        if (mActionBarAdapter != null && !mActionBarAdapter.isSearchMode() && !mActionBarAdapter.isSelectionMode()
                && !isTalkbackOnAndOnPreLollipopMr1()
                && SharedPreferenceUtil.getShouldShowHamburgerPromo(getContext())) {
            if (FeatureHighlightHelper.showHamburgerFeatureHighlight(mActivity)) {
                SharedPreferenceUtil.setHamburgerPromoDisplayedBefore(getContext());
            }
        }
    }

    // There's a crash if we show feature highlight when Talkback is on, on API 21 and below.
    // See b/31180524.
    private boolean isTalkbackOnAndOnPreLollipopMr1() {
        return ((AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE))
                .isTouchExplorationEnabled() && !CompatUtils.isLollipopMr1Compatible();
    }

    private void bindListHeader(int numberOfContacts) {
        final ContactListFilter filter = getFilter();
        // If the phone has at least one Google account whose sync status is unsyncable or pending
        // or active, we have to make mAccountFilterContainer visible.
        if (!isSearchMode() && numberOfContacts <= 0 && shouldShowEmptyView(filter)) {
            if (filter != null && filter.isContactsFilterType()) {
                makeViewVisible(mEmptyHomeView);
            } else {
                makeViewVisible(mEmptyAccountView);
            }
            return;
        }
        makeViewVisible(mAccountFilterContainer);
        if (isSearchMode()) {
            hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer);
        } else if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
            bindListHeaderCustom(getListView(), mAccountFilterContainer);
        } else if (filter.filterType != ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
            final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(filter.accountName,
                    filter.accountType, filter.dataSet);
            bindListHeader(getContext(), getListView(), mAccountFilterContainer, accountWithDataSet,
                    numberOfContacts);
        } else {
            hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer);
        }
    }

    /**
     * If at least one Google account is unsyncable or its sync status is pending or active, we
     * should not show empty view even if the number of contacts is 0. We should show sync status
     * with empty list instead.
     */
    private boolean shouldShowEmptyView(ContactListFilter filter) {
        if (filter == null) {
            return true;
        }
        // TODO(samchen) : Check ContactListFilter.FILTER_TYPE_CUSTOM
        if (ContactListFilter.FILTER_TYPE_DEFAULT == filter.filterType
                || ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS == filter.filterType) {
            final List<AccountInfo> syncableAccounts = AccountTypeManager.getInstance(getContext())
                    .getWritableGoogleAccounts();

            if (syncableAccounts != null && syncableAccounts.size() > 0) {
                for (AccountInfo info : syncableAccounts) {
                    // Won't be null because Google accounts have a non-null name and type.
                    final Account account = info.getAccount().getAccountOrNull();
                    if (SyncUtil.isSyncStatusPendingOrActive(account)
                            || SyncUtil.isUnsyncableGoogleAccount(account)) {
                        return false;
                    }
                }
            }
        } else if (ContactListFilter.FILTER_TYPE_ACCOUNT == filter.filterType) {
            final Account account = new Account(filter.accountName, filter.accountType);
            return !(SyncUtil.isSyncStatusPendingOrActive(account) || SyncUtil.isUnsyncableGoogleAccount(account));
        }
        return true;
    }

    // Show the view that's specified by id and hide the other two.
    private void makeViewVisible(View view) {
        mEmptyAccountView.setVisibility(view == mEmptyAccountView ? View.VISIBLE : View.GONE);
        mEmptyHomeView.setVisibility(view == mEmptyHomeView ? View.VISIBLE : View.GONE);
        mAccountFilterContainer.setVisibility(view == mAccountFilterContainer ? View.VISIBLE : View.GONE);
    }

    public void scrollToTop() {
        if (getListView() != null) {
            getListView().setSelection(0);
        }
    }

    @Override
    protected void onItemClick(int position, long id) {
        final Uri uri = getAdapter().getContactUri(position);
        if (uri == null) {
            return;
        }
        if (getAdapter().isDisplayingCheckBoxes()) {
            super.onItemClick(position, id);
            return;
        } else {
            if (isSearchMode()) {
                mSearchResultClicked = true;
                Logger.logSearchEvent(createSearchStateForSearchResultClick(position));
            }
        }
        viewContact(position, uri, getAdapter().isEnterpriseContact(position));
    }

    @Override
    protected ContactListAdapter createListAdapter() {
        DefaultContactListAdapter adapter = new DefaultContactListAdapter(getContext());
        adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
        adapter.setDisplayPhotos(true);
        adapter.setPhotoPosition(ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false));
        return adapter;
    }

    @Override
    public ContactListFilter getFilter() {
        return mContactListFilterController.getFilter();
    }

    @Override
    protected View inflateView(LayoutInflater inflater, ViewGroup container) {
        final View view = inflater.inflate(R.layout.contact_list_content, null);

        mAccountFilterContainer = view.findViewById(R.id.account_filter_header_container);

        // Add empty main view and account view to list.
        final FrameLayout contactListLayout = (FrameLayout) view.findViewById(R.id.contact_list);
        mEmptyAccountView = getEmptyAccountView(inflater);
        mEmptyHomeView = getEmptyHomeView(inflater);
        contactListLayout.addView(mEmptyAccountView);
        contactListLayout.addView(mEmptyHomeView);

        return view;
    }

    private View getEmptyHomeView(LayoutInflater inflater) {
        final View emptyHomeView = inflater.inflate(R.layout.empty_home_view, null);
        // Set image margins.
        final ImageView image = (ImageView) emptyHomeView.findViewById(R.id.empty_home_image);
        final LayoutParams params = (LayoutParams) image.getLayoutParams();
        final int screenHeight = getResources().getDisplayMetrics().heightPixels;
        final int marginTop = screenHeight / 2
                - getResources().getDimensionPixelSize(R.dimen.empty_home_view_image_offset);
        params.setMargins(0, marginTop, 0, 0);
        params.gravity = Gravity.CENTER_HORIZONTAL;
        image.setLayoutParams(params);

        // Set up add contact button.
        final Button addContactButton = (Button) emptyHomeView.findViewById(R.id.add_contact_button);
        addContactButton.setOnClickListener(mAddContactListener);
        return emptyHomeView;
    }

    private View getEmptyAccountView(LayoutInflater inflater) {
        final View emptyAccountView = inflater.inflate(R.layout.empty_account_view, null);
        // Set image margins.
        final ImageView image = (ImageView) emptyAccountView.findViewById(R.id.empty_account_image);
        final LayoutParams params = (LayoutParams) image.getLayoutParams();
        final int height = getResources().getDisplayMetrics().heightPixels;
        final int divisor = getResources().getInteger(R.integer.empty_account_view_image_margin_divisor);
        final int offset = getResources().getDimensionPixelSize(R.dimen.empty_account_view_image_offset);
        params.setMargins(0, height / divisor + offset, 0, 0);
        params.gravity = Gravity.CENTER_HORIZONTAL;
        image.setLayoutParams(params);

        // Set up add contact button.
        final Button addContactButton = (Button) emptyAccountView.findViewById(R.id.add_contact_button);
        addContactButton.setOnClickListener(mAddContactListener);
        return emptyAccountView;
    }

    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        mIsRecreatedInstance = (savedState != null);
        mContactListFilterController = ContactListFilterController.getInstance(getContext());
        mContactListFilterController.checkFilterValidity(false);
        // Use FILTER_TYPE_ALL_ACCOUNTS filter if the instance is not a re-created one.
        // This is useful when user upgrades app while an account filter was
        // stored in sharedPreference in a previous version of Contacts app.
        final ContactListFilter filter = mIsRecreatedInstance ? getFilter()
                : AccountFilterUtil.createContactsFilter(getContext());
        setContactListFilter(filter);
    }

    @Override
    protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
        super.onCreateView(inflater, container);

        initSwipeRefreshLayout();

        // Putting the header view inside a container will allow us to make
        // it invisible later. See checkHeaderViewVisibility()
        final FrameLayout headerContainer = new FrameLayout(inflater.getContext());
        mSearchHeaderView = inflater.inflate(R.layout.search_header, null, false);
        headerContainer.addView(mSearchHeaderView);
        getListView().addHeaderView(headerContainer, null, false);
        checkHeaderViewVisibility();

        mSearchProgress = getView().findViewById(R.id.search_progress);
        mSearchProgressText = (TextView) mSearchHeaderView.findViewById(R.id.totalContactsText);

        mAlertContainer = getView().findViewById(R.id.alert_container);
        mAlertText = (TextView) mAlertContainer.findViewById(R.id.alert_text);
        mAlertDismissIcon = (ImageView) mAlertContainer.findViewById(R.id.alert_dismiss_icon);
        mAlertText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                turnSyncOn();
            }
        });
        mAlertDismissIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        mAlertContainer.setVisibility(View.GONE);
    }

    private void turnSyncOn() {
        final ContactListFilter filter = getFilter();
        if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
                && mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
            ContentResolver.setSyncAutomatically(new Account(filter.accountName, filter.accountType),
                    ContactsContract.AUTHORITY, true);
            mAlertContainer.setVisibility(View.GONE);
        } else {
            final EnableGlobalSyncDialogFragment dialog = new EnableGlobalSyncDialogFragment();
            dialog.show(this, filter);
        }
    }

    @Override
    public void onEnableAutoSync(ContactListFilter filter) {
        // Turn on auto-sync
        ContentResolver.setMasterSyncAutomatically(true);

        // This should be OK (won't block) because this only happens after a user action
        final List<AccountInfo> accountInfos = Futures.getUnchecked(mWritableAccountsFuture);
        // Also enable Contacts sync
        final List<AccountWithDataSet> accounts = AccountInfo.extractAccounts(accountInfos);
        final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
        if (syncableAccounts != null && syncableAccounts.size() > 0) {
            for (Account account : syncableAccounts) {
                ContentResolver.setSyncAutomatically(new Account(account.name, account.type),
                        ContactsContract.AUTHORITY, true);
            }
        }
        mAlertContainer.setVisibility(View.GONE);
    }

    private void dismiss() {
        if (mReasonSyncOff == SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF) {
            SharedPreferenceUtil.incNumOfDismissesForAutoSyncOff(getContext());
        } else if (mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
            SharedPreferenceUtil.incNumOfDismissesForAccountSyncOff(getContext(), getFilter().accountName);
        }
        mAlertContainer.setVisibility(View.GONE);
    }

    private void initSwipeRefreshLayout() {
        mSwipeRefreshLayout = (SwipeRefreshLayout) mView.findViewById(R.id.swipe_refresh);
        if (mSwipeRefreshLayout == null) {
            return;
        }

        mSwipeRefreshLayout.setEnabled(true);
        // Request sync contacts
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                mHandler.removeCallbacks(mCancelRefresh);

                final boolean isNetworkConnected = SyncUtil.isNetworkConnected(getContext());
                if (!isNetworkConnected) {
                    mSwipeRefreshLayout.setRefreshing(false);
                    ((PeopleActivity) getActivity()).showConnectionErrorMsg();
                    return;
                }

                syncContacts(getFilter());
                mHandler.postDelayed(mCancelRefresh,
                        Flags.getInstance().getInteger(Experiments.PULL_TO_REFRESH_CANCEL_REFRESH_MILLIS));
            }
        });
        mSwipeRefreshLayout.setColorSchemeResources(R.color.swipe_refresh_color1, R.color.swipe_refresh_color2,
                R.color.swipe_refresh_color3, R.color.swipe_refresh_color4);
        mSwipeRefreshLayout
                .setDistanceToTriggerSync((int) getResources().getDimension(R.dimen.pull_to_refresh_distance));
    }

    /**
     * Request sync for the Google accounts (not include Google+ accounts) specified by the given
     * filter.
     */
    private void syncContacts(ContactListFilter filter) {
        if (filter == null) {
            return;
        }

        final Bundle bundle = new Bundle();
        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);

        final List<AccountWithDataSet> accounts = AccountInfo
                .extractAccounts(Futures.getUnchecked(mWritableAccountsFuture));
        final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
        if (syncableAccounts != null && syncableAccounts.size() > 0) {
            for (Account account : syncableAccounts) {
                // We can prioritize Contacts sync if sync is not initialized yet.
                if (!SyncUtil.isSyncStatusPendingOrActive(account) || SyncUtil.isUnsyncableGoogleAccount(account)) {
                    ContentResolver.requestSync(account, ContactsContract.AUTHORITY, bundle);
                }
            }
        }
    }

    private void setSyncOffAlert() {
        final ContactListFilter filter = getFilter();
        final Account account = filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
                && filter.isGoogleAccountType() ? new Account(filter.accountName, filter.accountType) : null;

        if (account == null && !filter.isContactsFilterType()) {
            mAlertContainer.setVisibility(View.GONE);
        } else {
            mReasonSyncOff = SyncUtil.calculateReasonSyncOff(getContext(), account);
            final boolean isAlertVisible = SyncUtil.isAlertVisible(getContext(), account, mReasonSyncOff);
            setSyncOffMsg(mReasonSyncOff);
            mAlertContainer.setVisibility(isAlertVisible ? View.VISIBLE : View.GONE);
        }
    }

    private void setSyncOffMsg(int reason) {
        final Resources resources = getResources();
        switch (reason) {
        case SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF:
            mAlertText.setText(resources.getString(R.string.auto_sync_off));
            break;
        case SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF:
            mAlertText.setText(resources.getString(R.string.account_sync_off));
            break;
        default:
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        mActivity = (PeopleActivity) getActivity();
        mActionBarAdapter = new ActionBarAdapter(mActivity, mActionBarListener, mActivity.getSupportActionBar(),
                mActivity.getToolbar(), R.string.enter_contact_name);
        mActionBarAdapter.setShowHomeIcon(true);
        initializeActionBarAdapter(savedInstanceState);
        if (isSearchMode()) {
            mActionBarAdapter.setFocusOnSearchView();
        }

        setCheckBoxListListener(new CheckBoxListListener());
        setOnContactListActionListener(new ContactBrowserActionListener());
        if (savedInstanceState != null) {
            if (savedInstanceState.getBoolean(KEY_DELETION_IN_PROGRESS)) {
                deleteSelectedContacts();
            }
            mSearchResultClicked = savedInstanceState.getBoolean(KEY_SEARCH_RESULT_CLICKED);
        }

        setDirectorySearchMode();
        mCanSetActionBar = true;
    }

    public void initializeActionBarAdapter(Bundle savedInstanceState) {
        if (mActionBarAdapter != null) {
            mActionBarAdapter.initialize(savedInstanceState, mContactsRequest);
        }
    }

    private void configureFragment() {
        if (mFragmentInitialized && !mFromOnNewIntent) {
            return;
        }

        mFragmentInitialized = true;

        if (mFromOnNewIntent || !mIsRecreatedInstance) {
            mFromOnNewIntent = false;
            configureFragmentForRequest();
        }

        configureContactListFragment();
    }

    private void configureFragmentForRequest() {
        ContactListFilter filter = null;
        final int actionCode = mContactsRequest.getActionCode();
        boolean searchMode = mContactsRequest.isSearchMode();
        switch (actionCode) {
        case ContactsRequest.ACTION_ALL_CONTACTS:
            filter = AccountFilterUtil.createContactsFilter(getContext());
            break;
        case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
            filter = ContactListFilter.createFilterWithType(ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
            break;

        case ContactsRequest.ACTION_FREQUENT:
        case ContactsRequest.ACTION_STREQUENT:
        case ContactsRequest.ACTION_STARRED:
        case ContactsRequest.ACTION_VIEW_CONTACT:
        default:
            break;
        }

        if (filter != null) {
            setContactListFilter(filter);
            searchMode = false;
        }

        if (mContactsRequest.getContactUri() != null) {
            searchMode = false;
        }

        mActionBarAdapter.setSearchMode(searchMode);
        configureContactListFragmentForRequest();
    }

    private void configureContactListFragmentForRequest() {
        final Uri contactUri = mContactsRequest.getContactUri();
        if (contactUri != null) {
            setSelectedContactUri(contactUri);
        }

        setQueryString(mActionBarAdapter.getQueryString(), true);
        setVisibleScrollbarEnabled(!isSearchMode());
    }

    private void setDirectorySearchMode() {
        if (mContactsRequest != null && mContactsRequest.isDirectorySearchEnabled()) {
            setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
        } else {
            setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        configureFragment();
        maybeShowHamburgerFeatureHighlight();
        // Re-register the listener, which may have been cleared when onSaveInstanceState was
        // called. See also: onSaveInstanceState
        mActionBarAdapter.setListener(mActionBarListener);
        mDisableOptionItemSelected = false;
        maybeHideCheckBoxes();

        mWritableAccountsFuture = AccountTypeManager.getInstance(getContext())
                .filterAccountsAsync(AccountTypeManager.writableFilter());
    }

    private void maybeHideCheckBoxes() {
        if (!mActionBarAdapter.isSelectionMode()) {
            displayCheckBoxes(false);
        }
    }

    public ActionBarAdapter getActionBarAdapter() {
        return mActionBarAdapter;
    }

    @Override
    protected void setSearchMode(boolean flag) {
        super.setSearchMode(flag);
        checkHeaderViewVisibility();
        if (!flag)
            showSearchProgress(false);
    }

    /** Show or hide the directory-search progress spinner. */
    private void showSearchProgress(boolean show) {
        if (mSearchProgress != null) {
            mSearchProgress.setVisibility(show ? View.VISIBLE : View.GONE);
        }
    }

    private void checkHeaderViewVisibility() {
        // Hide the search header by default.
        if (mSearchHeaderView != null) {
            mSearchHeaderView.setVisibility(View.GONE);
        }
    }

    @Override
    protected void setListHeader() {
        if (!isSearchMode()) {
            return;
        }
        ContactListAdapter adapter = getAdapter();
        if (adapter == null) {
            return;
        }

        // In search mode we only display the header if there is nothing found
        if (TextUtils.isEmpty(getQueryString()) || !adapter.areAllPartitionsEmpty()) {
            mSearchHeaderView.setVisibility(View.GONE);
            showSearchProgress(false);
        } else {
            mSearchHeaderView.setVisibility(View.VISIBLE);
            if (adapter.isLoading()) {
                mSearchProgressText.setText(R.string.search_results_searching);
                showSearchProgress(true);
            } else {
                mSearchProgressText.setText(R.string.listFoundAllContactsZero);
                mSearchProgressText.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
                showSearchProgress(false);
            }
        }
    }

    public SwipeRefreshLayout getSwipeRefreshLayout() {
        return mSwipeRefreshLayout;
    }

    private final class CheckBoxListListener implements OnCheckBoxListActionListener {
        @Override
        public void onStartDisplayingCheckBoxes() {
            mActionBarAdapter.setSelectionMode(true);
            mActivity.invalidateOptionsMenu();
        }

        @Override
        public void onSelectedContactIdsChanged() {
            mActionBarAdapter.setSelectionCount(getSelectedContactIds().size());
            mActivity.invalidateOptionsMenu();
            mActionBarAdapter.updateOverflowButtonColor();
        }

        @Override
        public void onStopDisplayingCheckBoxes() {
            mActionBarAdapter.setSelectionMode(false);
        }
    }

    public void setFilterAndUpdateTitle(ContactListFilter filter) {
        setFilterAndUpdateTitle(filter, true);
    }

    private void setFilterAndUpdateTitle(ContactListFilter filter, boolean restoreSelectedUri) {
        setContactListFilter(filter);
        updateListFilter(filter, restoreSelectedUri);
        mActivity.setTitle(AccountFilterUtil.getActionBarTitleForFilter(mActivity, filter));

        // Alert user if sync is off and not dismissed before
        setSyncOffAlert();

        // Determine whether the account has pullToRefresh feature
        setSwipeRefreshLayoutEnabledOrNot(filter);
    }

    private void setSwipeRefreshLayoutEnabledOrNot(ContactListFilter filter) {
        final SwipeRefreshLayout swipeRefreshLayout = getSwipeRefreshLayout();
        if (swipeRefreshLayout == null) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Can not load swipeRefreshLayout, swipeRefreshLayout is null");
            }
            return;
        }

        swipeRefreshLayout.setRefreshing(false);
        swipeRefreshLayout.setEnabled(false);

        if (filter != null && !mActionBarAdapter.isSearchMode() && !mActionBarAdapter.isSelectionMode()) {
            if (filter.isSyncable() || (filter.shouldShowSyncState()
                    && SyncUtil.hasSyncableAccount(AccountTypeManager.getInstance(getContext())))) {
                swipeRefreshLayout.setEnabled(true);
            }
        }
    }

    private void configureContactListFragment() {
        // Filter may be changed when activity is in background.
        setFilterAndUpdateTitle(getFilter());
        setVerticalScrollbarPosition(getScrollBarPosition());
        setSelectionVisible(false);
        mActivity.invalidateOptionsMenu();
    }

    private int getScrollBarPosition() {
        final Locale locale = Locale.getDefault();
        final boolean isRTL = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
        return isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
    }

    private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
        ContactBrowserActionListener() {
        }

        @Override
        public void onSelectionChange() {
        }

        @Override
        public void onViewContactAction(int position, Uri contactLookupUri, boolean isEnterpriseContact) {
            if (isEnterpriseContact) {
                // No implicit intent as user may have a different contacts app in work profile.
                ContactsContract.QuickContact.showQuickContact(getContext(), new Rect(), contactLookupUri,
                        QuickContactActivity.MODE_FULLY_EXPANDED, null);
            } else {
                final int previousScreen;
                if (isSearchMode()) {
                    previousScreen = ScreenEvent.ScreenType.SEARCH;
                } else {
                    if (isAllContactsFilter(getFilter())) {
                        if (position < getAdapter().getNumberOfFavorites()) {
                            previousScreen = ScreenEvent.ScreenType.FAVORITES;
                        } else {
                            previousScreen = ScreenEvent.ScreenType.ALL_CONTACTS;
                        }
                    } else {
                        previousScreen = ScreenEvent.ScreenType.LIST_ACCOUNT;
                    }
                }

                Logger.logListEvent(ListEvent.ActionType.CLICK, /* listType */ getListTypeIncludingSearch(),
                        /* count */ getAdapter().getCount(), /* clickedIndex */ position, /* numSelected */ 0);

                ImplicitIntentsUtil.startQuickContact(getActivity(), contactLookupUri, previousScreen);
            }
        }

        @Override
        public void onDeleteContactAction(Uri contactUri) {
            ContactDeletionInteraction.start(mActivity, contactUri, false);
        }

        @Override
        public void onFinishAction() {
            mActivity.onBackPressed();
        }

        @Override
        public void onInvalidSelection() {
            ContactListFilter filter;
            ContactListFilter currentFilter = getFilter();
            if (currentFilter != null && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
                filter = AccountFilterUtil.createContactsFilter(getContext());
                setFilterAndUpdateTitle(filter);
            } else {
                filter = ContactListFilter.createFilterWithType(ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
                setFilterAndUpdateTitle(filter, /* restoreSelectedUri */ false);
            }
            setContactListFilter(filter);
        }
    }

    private boolean isAllContactsFilter(ContactListFilter filter) {
        return filter != null && filter.isContactsFilterType();
    }

    public void setContactsAvailable(boolean contactsAvailable) {
        mContactsAvailable = contactsAvailable;
    }

    /**
     * Set filter via ContactListFilterController
     */
    private void setContactListFilter(ContactListFilter filter) {
        mContactListFilterController.setContactListFilter(filter, /* persistent */ isAllContactsFilter(filter));
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        if (!mContactsAvailable || mActivity.isInSecondLevel()) {
            // If contacts aren't available or this fragment is not visible, hide all menu items.
            return;
        }
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.people_options, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        mOptionsMenuContactsAvailable = mContactsAvailable;
        if (!mOptionsMenuContactsAvailable) {
            return;
        }

        final boolean isSearchOrSelectionMode = mActionBarAdapter.isSearchMode()
                || mActionBarAdapter.isSelectionMode();
        makeMenuItemVisible(menu, R.id.menu_search, !isSearchOrSelectionMode);

        final boolean showSelectedContactOptions = mActionBarAdapter.isSelectionMode()
                && getSelectedContactIds().size() != 0;
        makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions);
        makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions);
        final boolean showLinkContactsOptions = mActionBarAdapter.isSelectionMode()
                && getSelectedContactIds().size() > 1;
        makeMenuItemVisible(menu, R.id.menu_join, showLinkContactsOptions);

        // Debug options need to be visible even in search mode.
        makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions && hasExportIntentHandler());

        // Light tint the icons for normal mode, dark tint for search or selection mode.
        for (int i = 0; i < menu.size(); ++i) {
            final Drawable icon = menu.getItem(i).getIcon();
            if (icon != null && !isSearchOrSelectionMode) {
                icon.mutate().setColorFilter(ContextCompat.getColor(getContext(), R.color.actionbar_icon_color),
                        PorterDuff.Mode.SRC_ATOP);
            }
        }
    }

    private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
        final MenuItem item = menu.findItem(itemId);
        if (item != null) {
            item.setVisible(visible);
        }
    }

    private boolean hasExportIntentHandler() {
        final Intent intent = new Intent();
        intent.setAction("com.android.providers.contacts.DUMP_DATABASE");
        final List<ResolveInfo> receivers = getContext().getPackageManager().queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        return receivers != null && receivers.size() > 0;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (mDisableOptionItemSelected) {
            return false;
        }

        final int id = item.getItemId();
        if (id == android.R.id.home) {
            if (mActionBarAdapter.isUpShowing()) {
                // "UP" icon press -- should be treated as "back".
                mActivity.onBackPressed();
            }
            return true;
        } else if (id == R.id.menu_search) {
            if (!mActionBarAdapter.isSelectionMode()) {
                mActionBarAdapter.setSearchMode(true);
            }
            return true;
        } else if (id == R.id.menu_share) {
            shareSelectedContacts();
            return true;
        } else if (id == R.id.menu_join) {
            Logger.logListEvent(ListEvent.ActionType.LINK, /* listType */ getListTypeIncludingSearch(),
                    /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
                    /* numSelected */ getAdapter().getSelectedContactIds().size());
            joinSelectedContacts();
            return true;
        } else if (id == R.id.menu_delete) {
            deleteSelectedContacts();
            return true;
        } else if (id == R.id.export_database) {
            final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
            ImplicitIntentsUtil.startActivityOutsideApp(getContext(), intent);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Share all contacts that are currently selected. This method is pretty inefficient for
     * handling large numbers of contacts. I don't expect this to be a problem.
     */
    private void shareSelectedContacts() {
        final StringBuilder uriListBuilder = new StringBuilder();
        for (Long contactId : getSelectedContactIds()) {
            final Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
            final Uri lookupUri = ContactsContract.Contacts.getLookupUri(getContext().getContentResolver(),
                    contactUri);
            if (lookupUri == null) {
                continue;
            }
            final List<String> pathSegments = lookupUri.getPathSegments();
            if (pathSegments.size() < 2) {
                continue;
            }
            final String lookupKey = pathSegments.get(pathSegments.size() - 2);
            if (uriListBuilder.length() > 0) {
                uriListBuilder.append(':');
            }
            uriListBuilder.append(Uri.encode(lookupKey));
        }
        if (uriListBuilder.length() == 0) {
            return;
        }
        final Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI,
                Uri.encode(uriListBuilder.toString()));
        final Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType(ContactsContract.Contacts.CONTENT_VCARD_TYPE);
        intent.putExtra(Intent.EXTRA_STREAM, uri);
        try {
            startActivityForResult(Intent.createChooser(intent, getResources()
                    .getQuantityString(R.plurals.title_share_via, /* quantity */ getSelectedContactIds().size())),
                    ACTIVITY_REQUEST_CODE_SHARE);
        } catch (final ActivityNotFoundException ex) {
            Toast.makeText(getContext(), R.string.share_error, Toast.LENGTH_SHORT).show();
        }
    }

    private void joinSelectedContacts() {
        final Context context = getContext();
        final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(context,
                getSelectedContactIdsArray());
        context.startService(intent);

        mActionBarAdapter.setSelectionMode(false);
    }

    private void deleteSelectedContacts() {
        final ContactMultiDeletionInteraction multiDeletionInteraction = ContactMultiDeletionInteraction.start(this,
                getSelectedContactIds());
        multiDeletionInteraction.setListener(new MultiDeleteListener());
        mIsDeletionInProgress = true;
    }

    private final class MultiDeleteListener implements MultiContactDeleteListener {
        @Override
        public void onDeletionFinished() {
            // The parameters count and numSelected are both the number of contacts before deletion.
            Logger.logListEvent(ListEvent.ActionType.DELETE, /* listType */ getListTypeIncludingSearch(),
                    /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
                    /* numSelected */ getSelectedContactIds().size());
            mActionBarAdapter.setSelectionMode(false);
            mIsDeletionInProgress = false;
        }
    }

    private int getListTypeIncludingSearch() {
        return isSearchMode() ? ListEvent.ListType.SEARCH_RESULT : getListType();
    }

    public void setParameters(ContactsRequest contactsRequest, boolean fromOnNewIntent) {
        mContactsRequest = contactsRequest;
        mFromOnNewIntent = fromOnNewIntent;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
        // anymore
        case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
            if (resultCode == Activity.RESULT_OK) {
                onPickerResult(data);
            }
        case ACTIVITY_REQUEST_CODE_SHARE:
            Logger.logListEvent(ListEvent.ActionType.SHARE, /* listType */ getListTypeIncludingSearch(),
                    /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
                    /* numSelected */ getAdapter().getSelectedContactIds().size());

            // TODO fix or remove multipicker code: ag/54762
            //                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
            //                    // Finish the activity if the sub activity was canceled as back key is used
            //                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
            //                    finish();
            //                }
            //                break;
        }
    }

    public boolean getOptionsMenuContactsAvailable() {
        return mOptionsMenuContactsAvailable;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
        // in order to avoid doing fragment transactions after it.
        // TODO Figure out a better way to deal with the issue (ag/120686).
        if (mActionBarAdapter != null) {
            mActionBarAdapter.setListener(null);
            mActionBarAdapter.onSaveInstanceState(outState);
        }
        mDisableOptionItemSelected = true;
        outState.putBoolean(KEY_DELETION_IN_PROGRESS, mIsDeletionInProgress);
        outState.putBoolean(KEY_SEARCH_RESULT_CLICKED, mSearchResultClicked);
    }

    @Override
    public void onPause() {
        mOptionsMenuContactsAvailable = false;
        super.onPause();
    }

    @Override
    public void onDestroy() {
        if (mActionBarAdapter != null) {
            mActionBarAdapter.setListener(null);
        }
        super.onDestroy();
    }

    public boolean onKeyDown(int unicodeChar) {
        if (mActionBarAdapter != null && mActionBarAdapter.isSelectionMode()) {
            // Ignore keyboard input when in selection mode.
            return true;
        }

        if (mActionBarAdapter != null && !mActionBarAdapter.isSearchMode()) {
            final String query = new String(new int[] { unicodeChar }, 0, 1);
            mActionBarAdapter.setSearchMode(true);
            mActionBarAdapter.setQueryString(query);
            return true;
        }

        return false;
    }

    public boolean canSetActionBar() {
        return mCanSetActionBar;
    }
}