Home | History | Annotate | Download | only in list
      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 package com.android.contacts.list;
     17 
     18 import android.accounts.Account;
     19 import android.app.Activity;
     20 import android.content.ActivityNotFoundException;
     21 import android.content.ContentResolver;
     22 import android.content.ContentUris;
     23 import android.content.Context;
     24 import android.content.CursorLoader;
     25 import android.content.Intent;
     26 import android.content.Loader;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ResolveInfo;
     29 import android.content.res.Resources;
     30 import android.database.Cursor;
     31 import android.graphics.PorterDuff;
     32 import android.graphics.Rect;
     33 import android.graphics.drawable.Drawable;
     34 import android.net.Uri;
     35 import android.os.Bundle;
     36 import android.os.Handler;
     37 import android.provider.ContactsContract;
     38 import android.provider.ContactsContract.Directory;
     39 import android.support.v4.content.ContextCompat;
     40 import android.support.v4.widget.SwipeRefreshLayout;
     41 import android.text.TextUtils;
     42 import android.util.Log;
     43 import android.view.Gravity;
     44 import android.view.LayoutInflater;
     45 import android.view.Menu;
     46 import android.view.MenuInflater;
     47 import android.view.MenuItem;
     48 import android.view.View;
     49 import android.view.ViewGroup;
     50 import android.view.accessibility.AccessibilityEvent;
     51 import android.view.accessibility.AccessibilityManager;
     52 import android.widget.Button;
     53 import android.widget.FrameLayout;
     54 import android.widget.ImageView;
     55 import android.widget.LinearLayout.LayoutParams;
     56 import android.widget.TextView;
     57 import android.widget.Toast;
     58 
     59 import com.android.contacts.ContactSaveService;
     60 import com.android.contacts.Experiments;
     61 import com.android.contacts.R;
     62 import com.android.contacts.activities.ActionBarAdapter;
     63 import com.android.contacts.activities.PeopleActivity;
     64 import com.android.contacts.compat.CompatUtils;
     65 import com.android.contacts.interactions.ContactDeletionInteraction;
     66 import com.android.contacts.interactions.ContactMultiDeletionInteraction;
     67 import com.android.contacts.interactions.ContactMultiDeletionInteraction.MultiContactDeleteListener;
     68 import com.android.contacts.logging.ListEvent;
     69 import com.android.contacts.logging.Logger;
     70 import com.android.contacts.logging.ScreenEvent;
     71 import com.android.contacts.model.AccountTypeManager;
     72 import com.android.contacts.model.account.AccountInfo;
     73 import com.android.contacts.model.account.AccountWithDataSet;
     74 import com.android.contacts.quickcontact.QuickContactActivity;
     75 import com.android.contacts.util.AccountFilterUtil;
     76 import com.android.contacts.util.ImplicitIntentsUtil;
     77 import com.android.contacts.util.SharedPreferenceUtil;
     78 import com.android.contacts.util.SyncUtil;
     79 import com.android.contactsbind.FeatureHighlightHelper;
     80 import com.android.contactsbind.experiments.Flags;
     81 import com.google.common.util.concurrent.Futures;
     82 
     83 import java.util.List;
     84 import java.util.Locale;
     85 import java.util.concurrent.Future;
     86 
     87 /**
     88  * Fragment containing a contact list used for browsing (as compared to
     89  * picking a contact with one of the PICK intents).
     90  */
     91 public class DefaultContactBrowseListFragment extends ContactBrowseListFragment
     92         implements EnableGlobalSyncDialogFragment.Listener {
     93 
     94     private static final String TAG = "DefaultListFragment";
     95     private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
     96     private static final String KEY_DELETION_IN_PROGRESS = "deletionInProgress";
     97     private static final String KEY_SEARCH_RESULT_CLICKED = "search_result_clicked";
     98 
     99     private static final int ACTIVITY_REQUEST_CODE_SHARE = 0;
    100 
    101     private View mSearchHeaderView;
    102     private View mSearchProgress;
    103     private View mEmptyAccountView;
    104     private View mEmptyHomeView;
    105     private View mAccountFilterContainer;
    106     private TextView mSearchProgressText;
    107 
    108     private SwipeRefreshLayout mSwipeRefreshLayout;
    109     private final Handler mHandler = new Handler();
    110     private final Runnable mCancelRefresh = new Runnable() {
    111         @Override
    112         public void run() {
    113             if (mSwipeRefreshLayout.isRefreshing()) {
    114                 mSwipeRefreshLayout.setRefreshing(false);
    115             }
    116         }
    117     };
    118 
    119     private View mAlertContainer;
    120     private TextView mAlertText;
    121     private ImageView mAlertDismissIcon;
    122     private int mReasonSyncOff = SyncUtil.SYNC_SETTING_SYNC_ON;
    123 
    124     private boolean mContactsAvailable;
    125     private boolean mEnableDebugMenuOptions;
    126     private boolean mIsRecreatedInstance;
    127     private boolean mOptionsMenuContactsAvailable;
    128 
    129     private boolean mCanSetActionBar = false;
    130 
    131     /**
    132      * If {@link #configureFragment()} is already called. Used to avoid calling it twice
    133      * in {@link #onResume()}.
    134      * (This initialization only needs to be done once in onResume() when the Activity was just
    135      * created from scratch -- i.e. onCreate() was just called)
    136      */
    137     private boolean mFragmentInitialized;
    138 
    139     private boolean mFromOnNewIntent;
    140 
    141     /**
    142      * This is to tell whether we need to restart ContactMultiDeletionInteraction and set listener.
    143      * if screen is rotated while deletion dialog is shown.
    144      */
    145     private boolean mIsDeletionInProgress;
    146 
    147     /**
    148      * This is to disable {@link #onOptionsItemSelected} when we trying to stop the
    149      * activity/fragment.
    150      */
    151     private boolean mDisableOptionItemSelected;
    152 
    153     private boolean mSearchResultClicked;
    154 
    155     private ActionBarAdapter mActionBarAdapter;
    156     private PeopleActivity mActivity;
    157     private ContactsRequest mContactsRequest;
    158     private ContactListFilterController mContactListFilterController;
    159 
    160     private Future<List<AccountInfo>> mWritableAccountsFuture;
    161 
    162     private final ActionBarAdapter.Listener mActionBarListener = new ActionBarAdapter.Listener() {
    163         @Override
    164         public void onAction(int action) {
    165             switch (action) {
    166                 case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
    167                     displayCheckBoxes(true);
    168                     startSearchOrSelectionMode();
    169                     break;
    170                 case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
    171                     if (!mIsRecreatedInstance) {
    172                         Logger.logScreenView(mActivity, ScreenEvent.ScreenType.SEARCH);
    173                     }
    174                     startSearchOrSelectionMode();
    175                     break;
    176                 case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE:
    177                     mActivity.showFabWithAnimation(/* showFab */ true);
    178                     break;
    179                 case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
    180                     // If queryString is empty, fragment data will not be reloaded,
    181                     // so hamburger promo should be checked now.
    182                     // Otherwise, promo should be checked and displayed after reloading, b/30706521.
    183                     if (TextUtils.isEmpty(getQueryString())) {
    184                         maybeShowHamburgerFeatureHighlight();
    185                     }
    186                     setQueryTextToFragment("");
    187                     maybeHideCheckBoxes();
    188                     mActivity.invalidateOptionsMenu();
    189                     mActivity.showFabWithAnimation(/* showFab */ true);
    190 
    191                     // Alert user if sync is off and not dismissed before
    192                     setSyncOffAlert();
    193 
    194                     // Determine whether the account has pullToRefresh feature
    195                     setSwipeRefreshLayoutEnabledOrNot(getFilter());
    196                     break;
    197                 case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
    198                     final String queryString = mActionBarAdapter.getQueryString();
    199                     setQueryTextToFragment(queryString);
    200                     updateDebugOptionsVisibility(
    201                             ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
    202                     break;
    203                 default:
    204                     throw new IllegalStateException("Unknown ActionBarAdapter action: " + action);
    205             }
    206         }
    207 
    208         private void startSearchOrSelectionMode() {
    209             configureContactListFragment();
    210             maybeHideCheckBoxes();
    211             mActivity.invalidateOptionsMenu();
    212             mActivity.showFabWithAnimation(/* showFab */ false);
    213 
    214             final Context context = getContext();
    215             if (!SharedPreferenceUtil.getHamburgerPromoTriggerActionHappenedBefore(context)) {
    216                 SharedPreferenceUtil.setHamburgerPromoTriggerActionHappenedBefore(context);
    217             }
    218         }
    219 
    220         private void updateDebugOptionsVisibility(boolean visible) {
    221             if (mEnableDebugMenuOptions != visible) {
    222                 mEnableDebugMenuOptions = visible;
    223                 mActivity.invalidateOptionsMenu();
    224             }
    225         }
    226 
    227         private void setQueryTextToFragment(String query) {
    228             setQueryString(query, true);
    229             setVisibleScrollbarEnabled(!isSearchMode());
    230         }
    231 
    232         @Override
    233         public void onUpButtonPressed() {
    234             mActivity.onBackPressed();
    235         }
    236     };
    237 
    238     private final View.OnClickListener mAddContactListener = new View.OnClickListener() {
    239         @Override
    240         public void onClick(View v) {
    241             AccountFilterUtil.startEditorIntent(getContext(), mActivity.getIntent(), getFilter());
    242         }
    243     };
    244 
    245     public DefaultContactBrowseListFragment() {
    246         setPhotoLoaderEnabled(true);
    247         // Don't use a QuickContactBadge. Just use a regular ImageView. Using a QuickContactBadge
    248         // inside the ListView prevents us from using MODE_FULLY_EXPANDED and messes up ripples.
    249         setQuickContactEnabled(false);
    250         setSectionHeaderDisplayEnabled(true);
    251         setVisibleScrollbarEnabled(true);
    252         setDisplayDirectoryHeader(false);
    253         setHasOptionsMenu(true);
    254     }
    255 
    256     /**
    257      * Whether a search result was clicked by the user. Tracked so that we can distinguish
    258      * between exiting the search mode after a result was clicked from exiting w/o clicking
    259      * any search result.
    260      */
    261     public boolean wasSearchResultClicked() {
    262         return mSearchResultClicked;
    263     }
    264 
    265     /**
    266      * Resets whether a search result was clicked by the user to false.
    267      */
    268     public void resetSearchResultClicked() {
    269         mSearchResultClicked = false;
    270     }
    271 
    272     @Override
    273     public CursorLoader createCursorLoader(Context context) {
    274         return new FavoritesAndContactsLoader(context);
    275     }
    276 
    277     @Override
    278     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    279         if (loader.getId() == Directory.DEFAULT) {
    280             bindListHeader(data == null ? 0 : data.getCount());
    281         }
    282         super.onLoadFinished(loader, data);
    283         if (!isSearchMode()) {
    284             maybeShowHamburgerFeatureHighlight();
    285         }
    286         if (mActionBarAdapter != null) {
    287             mActionBarAdapter.updateOverflowButtonColor();
    288         }
    289     }
    290 
    291     private void maybeShowHamburgerFeatureHighlight() {
    292         if (mActionBarAdapter!= null && !mActionBarAdapter.isSearchMode()
    293                 && !mActionBarAdapter.isSelectionMode()
    294                 && !isTalkbackOnAndOnPreLollipopMr1()
    295                 && SharedPreferenceUtil.getShouldShowHamburgerPromo(getContext())) {
    296             if (FeatureHighlightHelper.showHamburgerFeatureHighlight(mActivity)) {
    297                 SharedPreferenceUtil.setHamburgerPromoDisplayedBefore(getContext());
    298             }
    299         }
    300     }
    301 
    302     // There's a crash if we show feature highlight when Talkback is on, on API 21 and below.
    303     // See b/31180524.
    304     private boolean isTalkbackOnAndOnPreLollipopMr1(){
    305         return ((AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE))
    306                 .isTouchExplorationEnabled()
    307                     && !CompatUtils.isLollipopMr1Compatible();
    308     }
    309 
    310     private void bindListHeader(int numberOfContacts) {
    311         final ContactListFilter filter = getFilter();
    312         // If the phone has at least one Google account whose sync status is unsyncable or pending
    313         // or active, we have to make mAccountFilterContainer visible.
    314         if (!isSearchMode() && numberOfContacts <= 0 && shouldShowEmptyView(filter)) {
    315             if (filter != null && filter.isContactsFilterType()) {
    316                 makeViewVisible(mEmptyHomeView);
    317             } else {
    318                 makeViewVisible(mEmptyAccountView);
    319             }
    320             return;
    321         }
    322         makeViewVisible(mAccountFilterContainer);
    323         if (isSearchMode()) {
    324             hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer);
    325         } else if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
    326             bindListHeaderCustom(getListView(), mAccountFilterContainer);
    327         } else if (filter.filterType != ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
    328             final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
    329                     filter.accountName, filter.accountType, filter.dataSet);
    330             bindListHeader(getContext(), getListView(), mAccountFilterContainer,
    331                     accountWithDataSet, numberOfContacts);
    332         } else {
    333             hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer);
    334         }
    335     }
    336 
    337     /**
    338      * If at least one Google account is unsyncable or its sync status is pending or active, we
    339      * should not show empty view even if the number of contacts is 0. We should show sync status
    340      * with empty list instead.
    341      */
    342     private boolean shouldShowEmptyView(ContactListFilter filter) {
    343         if (filter == null) {
    344             return true;
    345         }
    346         // TODO(samchen) : Check ContactListFilter.FILTER_TYPE_CUSTOM
    347         if (ContactListFilter.FILTER_TYPE_DEFAULT == filter.filterType
    348                 || ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS == filter.filterType) {
    349             final List<AccountInfo> syncableAccounts =
    350                     AccountTypeManager.getInstance(getContext()).getWritableGoogleAccounts();
    351 
    352             if (syncableAccounts != null && syncableAccounts.size() > 0) {
    353                 for (AccountInfo info : syncableAccounts) {
    354                     // Won't be null because Google accounts have a non-null name and type.
    355                     final Account account = info.getAccount().getAccountOrNull();
    356                     if (SyncUtil.isSyncStatusPendingOrActive(account)
    357                             || SyncUtil.isUnsyncableGoogleAccount(account)) {
    358                         return false;
    359                     }
    360                 }
    361             }
    362         } else if (ContactListFilter.FILTER_TYPE_ACCOUNT == filter.filterType) {
    363             final Account account = new Account(filter.accountName, filter.accountType);
    364             return !(SyncUtil.isSyncStatusPendingOrActive(account)
    365                     || SyncUtil.isUnsyncableGoogleAccount(account));
    366         }
    367         return true;
    368     }
    369 
    370     // Show the view that's specified by id and hide the other two.
    371     private void makeViewVisible(View view) {
    372         mEmptyAccountView.setVisibility(view == mEmptyAccountView ? View.VISIBLE : View.GONE);
    373         mEmptyHomeView.setVisibility(view == mEmptyHomeView ? View.VISIBLE : View.GONE);
    374         mAccountFilterContainer.setVisibility(
    375                 view == mAccountFilterContainer ? View.VISIBLE : View.GONE);
    376     }
    377 
    378     public void scrollToTop() {
    379         if (getListView() != null) {
    380             getListView().setSelection(0);
    381         }
    382     }
    383 
    384     @Override
    385     protected void onItemClick(int position, long id) {
    386         final Uri uri = getAdapter().getContactUri(position);
    387         if (uri == null) {
    388             return;
    389         }
    390         if (getAdapter().isDisplayingCheckBoxes()) {
    391             super.onItemClick(position, id);
    392             return;
    393         } else {
    394             if (isSearchMode()) {
    395                 mSearchResultClicked = true;
    396                 Logger.logSearchEvent(createSearchStateForSearchResultClick(position));
    397             }
    398         }
    399         viewContact(position, uri, getAdapter().isEnterpriseContact(position));
    400     }
    401 
    402     @Override
    403     protected ContactListAdapter createListAdapter() {
    404         DefaultContactListAdapter adapter = new DefaultContactListAdapter(getContext());
    405         adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
    406         adapter.setDisplayPhotos(true);
    407         adapter.setPhotoPosition(
    408                 ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false));
    409         return adapter;
    410     }
    411 
    412     @Override
    413     public ContactListFilter getFilter() {
    414         return mContactListFilterController.getFilter();
    415     }
    416 
    417     @Override
    418     protected View inflateView(LayoutInflater inflater, ViewGroup container) {
    419         final View view = inflater.inflate(R.layout.contact_list_content, null);
    420 
    421         mAccountFilterContainer = view.findViewById(R.id.account_filter_header_container);
    422 
    423         // Add empty main view and account view to list.
    424         final FrameLayout contactListLayout = (FrameLayout) view.findViewById(R.id.contact_list);
    425         mEmptyAccountView = getEmptyAccountView(inflater);
    426         mEmptyHomeView = getEmptyHomeView(inflater);
    427         contactListLayout.addView(mEmptyAccountView);
    428         contactListLayout.addView(mEmptyHomeView);
    429 
    430         return view;
    431     }
    432 
    433     private View getEmptyHomeView(LayoutInflater inflater) {
    434         final View emptyHomeView = inflater.inflate(R.layout.empty_home_view, null);
    435         // Set image margins.
    436         final ImageView image = (ImageView) emptyHomeView.findViewById(R.id.empty_home_image);
    437         final LayoutParams params = (LayoutParams) image.getLayoutParams();
    438         final int screenHeight = getResources().getDisplayMetrics().heightPixels;
    439         final int marginTop = screenHeight / 2 -
    440                 getResources().getDimensionPixelSize(R.dimen.empty_home_view_image_offset) ;
    441         params.setMargins(0, marginTop, 0, 0);
    442         params.gravity = Gravity.CENTER_HORIZONTAL;
    443         image.setLayoutParams(params);
    444 
    445         // Set up add contact button.
    446         final Button addContactButton =
    447                 (Button) emptyHomeView.findViewById(R.id.add_contact_button);
    448         addContactButton.setOnClickListener(mAddContactListener);
    449         return emptyHomeView;
    450     }
    451 
    452     private View getEmptyAccountView(LayoutInflater inflater) {
    453         final View emptyAccountView = inflater.inflate(R.layout.empty_account_view, null);
    454         // Set image margins.
    455         final ImageView image = (ImageView) emptyAccountView.findViewById(R.id.empty_account_image);
    456         final LayoutParams params = (LayoutParams) image.getLayoutParams();
    457         final int height = getResources().getDisplayMetrics().heightPixels;
    458         final int divisor =
    459                 getResources().getInteger(R.integer.empty_account_view_image_margin_divisor);
    460         final int offset =
    461                 getResources().getDimensionPixelSize(R.dimen.empty_account_view_image_offset);
    462         params.setMargins(0, height / divisor + offset, 0, 0);
    463         params.gravity = Gravity.CENTER_HORIZONTAL;
    464         image.setLayoutParams(params);
    465 
    466         // Set up add contact button.
    467         final Button addContactButton =
    468                 (Button) emptyAccountView.findViewById(R.id.add_contact_button);
    469         addContactButton.setOnClickListener(mAddContactListener);
    470         return emptyAccountView;
    471     }
    472 
    473     @Override
    474     public void onCreate(Bundle savedState) {
    475         super.onCreate(savedState);
    476         mIsRecreatedInstance = (savedState != null);
    477         mContactListFilterController = ContactListFilterController.getInstance(getContext());
    478         mContactListFilterController.checkFilterValidity(false);
    479         // Use FILTER_TYPE_ALL_ACCOUNTS filter if the instance is not a re-created one.
    480         // This is useful when user upgrades app while an account filter was
    481         // stored in sharedPreference in a previous version of Contacts app.
    482         final ContactListFilter filter = mIsRecreatedInstance
    483                 ? getFilter()
    484                 : AccountFilterUtil.createContactsFilter(getContext());
    485         setContactListFilter(filter);
    486     }
    487 
    488     @Override
    489     protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
    490         super.onCreateView(inflater, container);
    491 
    492         initSwipeRefreshLayout();
    493 
    494         // Putting the header view inside a container will allow us to make
    495         // it invisible later. See checkHeaderViewVisibility()
    496         final FrameLayout headerContainer = new FrameLayout(inflater.getContext());
    497         mSearchHeaderView = inflater.inflate(R.layout.search_header, null, false);
    498         headerContainer.addView(mSearchHeaderView);
    499         getListView().addHeaderView(headerContainer, null, false);
    500         checkHeaderViewVisibility();
    501 
    502         mSearchProgress = getView().findViewById(R.id.search_progress);
    503         mSearchProgressText = (TextView) mSearchHeaderView.findViewById(R.id.totalContactsText);
    504 
    505         mAlertContainer = getView().findViewById(R.id.alert_container);
    506         mAlertText = (TextView) mAlertContainer.findViewById(R.id.alert_text);
    507         mAlertDismissIcon = (ImageView) mAlertContainer.findViewById(R.id.alert_dismiss_icon);
    508         mAlertText.setOnClickListener(new View.OnClickListener() {
    509             @Override
    510             public void onClick(View v) {
    511                 turnSyncOn();
    512             }
    513         });
    514         mAlertDismissIcon.setOnClickListener(new View.OnClickListener() {
    515             @Override
    516             public void onClick(View v) {
    517                 dismiss();
    518             }
    519         });
    520 
    521         mAlertContainer.setVisibility(View.GONE);
    522     }
    523 
    524     private void turnSyncOn() {
    525         final ContactListFilter filter = getFilter();
    526         if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
    527                 && mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
    528             ContentResolver.setSyncAutomatically(
    529                     new Account(filter.accountName, filter.accountType),
    530                     ContactsContract.AUTHORITY, true);
    531             mAlertContainer.setVisibility(View.GONE);
    532         } else {
    533             final EnableGlobalSyncDialogFragment dialog = new
    534                     EnableGlobalSyncDialogFragment();
    535             dialog.show(this, filter);
    536         }
    537     }
    538 
    539     @Override
    540     public void onEnableAutoSync(ContactListFilter filter) {
    541         // Turn on auto-sync
    542         ContentResolver.setMasterSyncAutomatically(true);
    543 
    544         // This should be OK (won't block) because this only happens after a user action
    545         final List<AccountInfo> accountInfos = Futures.getUnchecked(mWritableAccountsFuture);
    546         // Also enable Contacts sync
    547         final List<AccountWithDataSet> accounts = AccountInfo.extractAccounts(accountInfos);
    548         final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
    549         if (syncableAccounts != null && syncableAccounts.size() > 0) {
    550             for (Account account : syncableAccounts) {
    551                 ContentResolver.setSyncAutomatically(new Account(account.name, account.type),
    552                         ContactsContract.AUTHORITY, true);
    553             }
    554         }
    555         mAlertContainer.setVisibility(View.GONE);
    556     }
    557 
    558     private void dismiss() {
    559         if (mReasonSyncOff == SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF) {
    560             SharedPreferenceUtil.incNumOfDismissesForAutoSyncOff(getContext());
    561         } else if (mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
    562             SharedPreferenceUtil.incNumOfDismissesForAccountSyncOff(
    563                     getContext(), getFilter().accountName);
    564         }
    565         mAlertContainer.setVisibility(View.GONE);
    566     }
    567 
    568     private void initSwipeRefreshLayout() {
    569         mSwipeRefreshLayout = (SwipeRefreshLayout) mView.findViewById(R.id.swipe_refresh);
    570         if (mSwipeRefreshLayout == null) {
    571             return;
    572         }
    573 
    574         mSwipeRefreshLayout.setEnabled(true);
    575         // Request sync contacts
    576         mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    577             @Override
    578             public void onRefresh() {
    579                 mHandler.removeCallbacks(mCancelRefresh);
    580 
    581                 final boolean isNetworkConnected = SyncUtil.isNetworkConnected(getContext());
    582                 if (!isNetworkConnected) {
    583                     mSwipeRefreshLayout.setRefreshing(false);
    584                     ((PeopleActivity)getActivity()).showConnectionErrorMsg();
    585                     return;
    586                 }
    587 
    588                 syncContacts(getFilter());
    589                 mHandler.postDelayed(mCancelRefresh, Flags.getInstance()
    590                         .getInteger(Experiments.PULL_TO_REFRESH_CANCEL_REFRESH_MILLIS));
    591             }
    592         });
    593         mSwipeRefreshLayout.setColorSchemeResources(
    594                 R.color.swipe_refresh_color1,
    595                 R.color.swipe_refresh_color2,
    596                 R.color.swipe_refresh_color3,
    597                 R.color.swipe_refresh_color4);
    598         mSwipeRefreshLayout.setDistanceToTriggerSync(
    599                 (int) getResources().getDimension(R.dimen.pull_to_refresh_distance));
    600     }
    601 
    602     /**
    603      * Request sync for the Google accounts (not include Google+ accounts) specified by the given
    604      * filter.
    605      */
    606     private void syncContacts(ContactListFilter filter) {
    607         if (filter == null) {
    608             return;
    609         }
    610 
    611         final Bundle bundle = new Bundle();
    612         bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
    613         bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    614 
    615         final List<AccountWithDataSet> accounts = AccountInfo.extractAccounts(
    616                 Futures.getUnchecked(mWritableAccountsFuture));
    617         final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
    618         if (syncableAccounts != null && syncableAccounts.size() > 0) {
    619             for (Account account : syncableAccounts) {
    620                 // We can prioritize Contacts sync if sync is not initialized yet.
    621                 if (!SyncUtil.isSyncStatusPendingOrActive(account)
    622                         || SyncUtil.isUnsyncableGoogleAccount(account)) {
    623                     ContentResolver.requestSync(account, ContactsContract.AUTHORITY, bundle);
    624                 }
    625             }
    626         }
    627     }
    628 
    629     private void setSyncOffAlert() {
    630         final ContactListFilter filter = getFilter();
    631         final Account account =  filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
    632                 && filter.isGoogleAccountType()
    633                 ? new Account(filter.accountName, filter.accountType) : null;
    634 
    635         if (account == null && !filter.isContactsFilterType()) {
    636             mAlertContainer.setVisibility(View.GONE);
    637         } else {
    638             mReasonSyncOff = SyncUtil.calculateReasonSyncOff(getContext(), account);
    639             final boolean isAlertVisible =
    640                     SyncUtil.isAlertVisible(getContext(), account, mReasonSyncOff);
    641             setSyncOffMsg(mReasonSyncOff);
    642             mAlertContainer.setVisibility(isAlertVisible ? View.VISIBLE : View.GONE);
    643         }
    644     }
    645 
    646     private void setSyncOffMsg(int reason) {
    647         final Resources resources = getResources();
    648         switch (reason) {
    649             case SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF:
    650                 mAlertText.setText(resources.getString(R.string.auto_sync_off));
    651                 break;
    652             case SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF:
    653                 mAlertText.setText(resources.getString(R.string.account_sync_off));
    654                 break;
    655             default:
    656         }
    657     }
    658 
    659     @Override
    660     public void onActivityCreated(Bundle savedInstanceState) {
    661         super.onActivityCreated(savedInstanceState);
    662 
    663         mActivity = (PeopleActivity) getActivity();
    664         mActionBarAdapter = new ActionBarAdapter(mActivity, mActionBarListener,
    665                 mActivity.getSupportActionBar(), mActivity.getToolbar(),
    666                 R.string.enter_contact_name);
    667         mActionBarAdapter.setShowHomeIcon(true);
    668         initializeActionBarAdapter(savedInstanceState);
    669         if (isSearchMode()) {
    670             mActionBarAdapter.setFocusOnSearchView();
    671         }
    672 
    673         setCheckBoxListListener(new CheckBoxListListener());
    674         setOnContactListActionListener(new ContactBrowserActionListener());
    675         if (savedInstanceState != null) {
    676             if (savedInstanceState.getBoolean(KEY_DELETION_IN_PROGRESS)) {
    677                 deleteSelectedContacts();
    678             }
    679             mSearchResultClicked = savedInstanceState.getBoolean(KEY_SEARCH_RESULT_CLICKED);
    680         }
    681 
    682         setDirectorySearchMode();
    683         mCanSetActionBar = true;
    684     }
    685 
    686     public void initializeActionBarAdapter(Bundle savedInstanceState) {
    687         if (mActionBarAdapter != null) {
    688             mActionBarAdapter.initialize(savedInstanceState, mContactsRequest);
    689         }
    690     }
    691 
    692     private void configureFragment() {
    693         if (mFragmentInitialized && !mFromOnNewIntent) {
    694             return;
    695         }
    696 
    697         mFragmentInitialized = true;
    698 
    699         if (mFromOnNewIntent || !mIsRecreatedInstance) {
    700             mFromOnNewIntent = false;
    701             configureFragmentForRequest();
    702         }
    703 
    704         configureContactListFragment();
    705     }
    706 
    707     private void configureFragmentForRequest() {
    708         ContactListFilter filter = null;
    709         final int actionCode = mContactsRequest.getActionCode();
    710         boolean searchMode = mContactsRequest.isSearchMode();
    711         switch (actionCode) {
    712             case ContactsRequest.ACTION_ALL_CONTACTS:
    713                 filter = AccountFilterUtil.createContactsFilter(getContext());
    714                 break;
    715             case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
    716                 filter = ContactListFilter.createFilterWithType(
    717                         ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
    718                 break;
    719 
    720             case ContactsRequest.ACTION_FREQUENT:
    721             case ContactsRequest.ACTION_STREQUENT:
    722             case ContactsRequest.ACTION_STARRED:
    723             case ContactsRequest.ACTION_VIEW_CONTACT:
    724             default:
    725                 break;
    726         }
    727 
    728         if (filter != null) {
    729             setContactListFilter(filter);
    730             searchMode = false;
    731         }
    732 
    733         if (mContactsRequest.getContactUri() != null) {
    734             searchMode = false;
    735         }
    736 
    737         mActionBarAdapter.setSearchMode(searchMode);
    738         configureContactListFragmentForRequest();
    739     }
    740 
    741     private void configureContactListFragmentForRequest() {
    742         final Uri contactUri = mContactsRequest.getContactUri();
    743         if (contactUri != null) {
    744             setSelectedContactUri(contactUri);
    745         }
    746 
    747         setQueryString(mActionBarAdapter.getQueryString(), true);
    748         setVisibleScrollbarEnabled(!isSearchMode());
    749     }
    750 
    751     private void setDirectorySearchMode() {
    752         if (mContactsRequest != null && mContactsRequest.isDirectorySearchEnabled()) {
    753             setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
    754         } else {
    755             setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
    756         }
    757     }
    758 
    759     @Override
    760     public void onResume() {
    761         super.onResume();
    762         configureFragment();
    763         maybeShowHamburgerFeatureHighlight();
    764         // Re-register the listener, which may have been cleared when onSaveInstanceState was
    765         // called. See also: onSaveInstanceState
    766         mActionBarAdapter.setListener(mActionBarListener);
    767         mDisableOptionItemSelected = false;
    768         maybeHideCheckBoxes();
    769 
    770         mWritableAccountsFuture = AccountTypeManager.getInstance(getContext()).filterAccountsAsync(
    771                 AccountTypeManager.writableFilter());
    772     }
    773 
    774     private void maybeHideCheckBoxes() {
    775         if (!mActionBarAdapter.isSelectionMode()) {
    776             displayCheckBoxes(false);
    777         }
    778     }
    779 
    780     public ActionBarAdapter getActionBarAdapter(){
    781         return mActionBarAdapter;
    782     }
    783 
    784     @Override
    785     protected void setSearchMode(boolean flag) {
    786         super.setSearchMode(flag);
    787         checkHeaderViewVisibility();
    788         if (!flag) showSearchProgress(false);
    789     }
    790 
    791     /** Show or hide the directory-search progress spinner. */
    792     private void showSearchProgress(boolean show) {
    793         if (mSearchProgress != null) {
    794             mSearchProgress.setVisibility(show ? View.VISIBLE : View.GONE);
    795         }
    796     }
    797 
    798     private void checkHeaderViewVisibility() {
    799         // Hide the search header by default.
    800         if (mSearchHeaderView != null) {
    801             mSearchHeaderView.setVisibility(View.GONE);
    802         }
    803     }
    804 
    805     @Override
    806     protected void setListHeader() {
    807         if (!isSearchMode()) {
    808             return;
    809         }
    810         ContactListAdapter adapter = getAdapter();
    811         if (adapter == null) {
    812             return;
    813         }
    814 
    815         // In search mode we only display the header if there is nothing found
    816         if (TextUtils.isEmpty(getQueryString()) || !adapter.areAllPartitionsEmpty()) {
    817             mSearchHeaderView.setVisibility(View.GONE);
    818             showSearchProgress(false);
    819         } else {
    820             mSearchHeaderView.setVisibility(View.VISIBLE);
    821             if (adapter.isLoading()) {
    822                 mSearchProgressText.setText(R.string.search_results_searching);
    823                 showSearchProgress(true);
    824             } else {
    825                 mSearchProgressText.setText(R.string.listFoundAllContactsZero);
    826                 mSearchProgressText.sendAccessibilityEvent(
    827                         AccessibilityEvent.TYPE_VIEW_SELECTED);
    828                 showSearchProgress(false);
    829             }
    830         }
    831     }
    832 
    833     public SwipeRefreshLayout getSwipeRefreshLayout() {
    834         return mSwipeRefreshLayout;
    835     }
    836 
    837     private final class CheckBoxListListener implements OnCheckBoxListActionListener {
    838         @Override
    839         public void onStartDisplayingCheckBoxes() {
    840             mActionBarAdapter.setSelectionMode(true);
    841             mActivity.invalidateOptionsMenu();
    842         }
    843 
    844         @Override
    845         public void onSelectedContactIdsChanged() {
    846             mActionBarAdapter.setSelectionCount(getSelectedContactIds().size());
    847             mActivity.invalidateOptionsMenu();
    848             mActionBarAdapter.updateOverflowButtonColor();
    849         }
    850 
    851         @Override
    852         public void onStopDisplayingCheckBoxes() {
    853             mActionBarAdapter.setSelectionMode(false);
    854         }
    855     }
    856 
    857     public void setFilterAndUpdateTitle(ContactListFilter filter) {
    858         setFilterAndUpdateTitle(filter, true);
    859     }
    860 
    861     private void setFilterAndUpdateTitle(ContactListFilter filter, boolean restoreSelectedUri) {
    862         setContactListFilter(filter);
    863         updateListFilter(filter, restoreSelectedUri);
    864         mActivity.setTitle(AccountFilterUtil.getActionBarTitleForFilter(mActivity, filter));
    865 
    866         // Alert user if sync is off and not dismissed before
    867         setSyncOffAlert();
    868 
    869         // Determine whether the account has pullToRefresh feature
    870         setSwipeRefreshLayoutEnabledOrNot(filter);
    871     }
    872 
    873     private void setSwipeRefreshLayoutEnabledOrNot(ContactListFilter filter) {
    874         final SwipeRefreshLayout swipeRefreshLayout = getSwipeRefreshLayout();
    875         if (swipeRefreshLayout == null) {
    876             if (Log.isLoggable(TAG, Log.DEBUG)) {
    877                 Log.d(TAG, "Can not load swipeRefreshLayout, swipeRefreshLayout is null");
    878             }
    879             return;
    880         }
    881 
    882         swipeRefreshLayout.setRefreshing(false);
    883         swipeRefreshLayout.setEnabled(false);
    884 
    885         if (filter != null && !mActionBarAdapter.isSearchMode()
    886                 && !mActionBarAdapter.isSelectionMode()) {
    887             if (filter.isSyncable()
    888                     || (filter.shouldShowSyncState()
    889                     && SyncUtil.hasSyncableAccount(AccountTypeManager.getInstance(getContext())))) {
    890                 swipeRefreshLayout.setEnabled(true);
    891             }
    892         }
    893     }
    894 
    895     private void configureContactListFragment() {
    896         // Filter may be changed when activity is in background.
    897         setFilterAndUpdateTitle(getFilter());
    898         setVerticalScrollbarPosition(getScrollBarPosition());
    899         setSelectionVisible(false);
    900         mActivity.invalidateOptionsMenu();
    901     }
    902 
    903     private int getScrollBarPosition() {
    904         final Locale locale = Locale.getDefault();
    905         final boolean isRTL =
    906                 TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
    907         return isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
    908     }
    909 
    910     private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
    911         ContactBrowserActionListener() {}
    912 
    913         @Override
    914         public void onSelectionChange() {
    915         }
    916 
    917         @Override
    918         public void onViewContactAction(int position, Uri contactLookupUri,
    919                 boolean isEnterpriseContact) {
    920             if (isEnterpriseContact) {
    921                 // No implicit intent as user may have a different contacts app in work profile.
    922                 ContactsContract.QuickContact.showQuickContact(getContext(), new Rect(),
    923                         contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED, null);
    924             } else {
    925                 final int previousScreen;
    926                 if (isSearchMode()) {
    927                     previousScreen = ScreenEvent.ScreenType.SEARCH;
    928                 } else {
    929                     if (isAllContactsFilter(getFilter())) {
    930                         if (position < getAdapter().getNumberOfFavorites()) {
    931                             previousScreen = ScreenEvent.ScreenType.FAVORITES;
    932                         } else {
    933                             previousScreen = ScreenEvent.ScreenType.ALL_CONTACTS;
    934                         }
    935                     } else {
    936                         previousScreen = ScreenEvent.ScreenType.LIST_ACCOUNT;
    937                     }
    938                 }
    939 
    940                 Logger.logListEvent(ListEvent.ActionType.CLICK,
    941                         /* listType */ getListTypeIncludingSearch(),
    942                         /* count */ getAdapter().getCount(),
    943                         /* clickedIndex */ position, /* numSelected */ 0);
    944 
    945                 ImplicitIntentsUtil.startQuickContact(
    946                         getActivity(), contactLookupUri, previousScreen);
    947             }
    948         }
    949 
    950         @Override
    951         public void onDeleteContactAction(Uri contactUri) {
    952             ContactDeletionInteraction.start(mActivity, contactUri, false);
    953         }
    954 
    955         @Override
    956         public void onFinishAction() {
    957             mActivity.onBackPressed();
    958         }
    959 
    960         @Override
    961         public void onInvalidSelection() {
    962             ContactListFilter filter;
    963             ContactListFilter currentFilter = getFilter();
    964             if (currentFilter != null
    965                     && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
    966                 filter = AccountFilterUtil.createContactsFilter(getContext());
    967                 setFilterAndUpdateTitle(filter);
    968             } else {
    969                 filter = ContactListFilter.createFilterWithType(
    970                         ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
    971                 setFilterAndUpdateTitle(filter, /* restoreSelectedUri */ false);
    972             }
    973             setContactListFilter(filter);
    974         }
    975     }
    976 
    977     private boolean isAllContactsFilter(ContactListFilter filter) {
    978         return filter != null && filter.isContactsFilterType();
    979     }
    980 
    981     public void setContactsAvailable(boolean contactsAvailable) {
    982         mContactsAvailable = contactsAvailable;
    983     }
    984 
    985     /**
    986      * Set filter via ContactListFilterController
    987      */
    988     private void setContactListFilter(ContactListFilter filter) {
    989         mContactListFilterController.setContactListFilter(filter,
    990                 /* persistent */ isAllContactsFilter(filter));
    991     }
    992 
    993     @Override
    994     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    995         if (!mContactsAvailable || mActivity.isInSecondLevel()) {
    996             // If contacts aren't available or this fragment is not visible, hide all menu items.
    997             return;
    998         }
    999         super.onCreateOptionsMenu(menu, inflater);
   1000         inflater.inflate(R.menu.people_options, menu);
   1001     }
   1002 
   1003     @Override
   1004     public void onPrepareOptionsMenu(Menu menu) {
   1005         mOptionsMenuContactsAvailable = mContactsAvailable;
   1006         if (!mOptionsMenuContactsAvailable) {
   1007             return;
   1008         }
   1009 
   1010         final boolean isSearchOrSelectionMode = mActionBarAdapter.isSearchMode()
   1011                 || mActionBarAdapter.isSelectionMode();
   1012         makeMenuItemVisible(menu, R.id.menu_search, !isSearchOrSelectionMode);
   1013 
   1014         final boolean showSelectedContactOptions = mActionBarAdapter.isSelectionMode()
   1015                 && getSelectedContactIds().size() != 0;
   1016         makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions);
   1017         makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions);
   1018         final boolean showLinkContactsOptions = mActionBarAdapter.isSelectionMode()
   1019                 && getSelectedContactIds().size() > 1;
   1020         makeMenuItemVisible(menu, R.id.menu_join, showLinkContactsOptions);
   1021 
   1022         // Debug options need to be visible even in search mode.
   1023         makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions &&
   1024                 hasExportIntentHandler());
   1025 
   1026         // Light tint the icons for normal mode, dark tint for search or selection mode.
   1027         for (int i = 0; i < menu.size(); ++i) {
   1028             final Drawable icon = menu.getItem(i).getIcon();
   1029             if (icon != null && !isSearchOrSelectionMode) {
   1030                 icon.mutate().setColorFilter(ContextCompat.getColor(getContext(),
   1031                         R.color.actionbar_icon_color), PorterDuff.Mode.SRC_ATOP);
   1032             }
   1033         }
   1034     }
   1035 
   1036     private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
   1037         final MenuItem item = menu.findItem(itemId);
   1038         if (item != null) {
   1039             item.setVisible(visible);
   1040         }
   1041     }
   1042 
   1043     private boolean hasExportIntentHandler() {
   1044         final Intent intent = new Intent();
   1045         intent.setAction("com.android.providers.contacts.DUMP_DATABASE");
   1046         final List<ResolveInfo> receivers =
   1047                 getContext().getPackageManager().queryIntentActivities(intent,
   1048                 PackageManager.MATCH_DEFAULT_ONLY);
   1049         return receivers != null && receivers.size() > 0;
   1050     }
   1051 
   1052     @Override
   1053     public boolean onOptionsItemSelected(MenuItem item) {
   1054         if (mDisableOptionItemSelected) {
   1055             return false;
   1056         }
   1057 
   1058         final int id = item.getItemId();
   1059         if (id == android.R.id.home) {
   1060             if (mActionBarAdapter.isUpShowing()) {
   1061                 // "UP" icon press -- should be treated as "back".
   1062                 mActivity.onBackPressed();
   1063             }
   1064             return true;
   1065         } else if (id == R.id.menu_search) {
   1066             if (!mActionBarAdapter.isSelectionMode()) {
   1067                 mActionBarAdapter.setSearchMode(true);
   1068             }
   1069             return true;
   1070         } else if (id == R.id.menu_share) {
   1071             shareSelectedContacts();
   1072             return true;
   1073         } else if (id == R.id.menu_join) {
   1074             Logger.logListEvent(ListEvent.ActionType.LINK,
   1075                         /* listType */ getListTypeIncludingSearch(),
   1076                         /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
   1077                         /* numSelected */ getAdapter().getSelectedContactIds().size());
   1078             joinSelectedContacts();
   1079             return true;
   1080         } else if (id == R.id.menu_delete) {
   1081             deleteSelectedContacts();
   1082             return true;
   1083         } else if (id == R.id.export_database) {
   1084             final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
   1085             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
   1086             ImplicitIntentsUtil.startActivityOutsideApp(getContext(), intent);
   1087             return true;
   1088         }
   1089         return super.onOptionsItemSelected(item);
   1090     }
   1091 
   1092     /**
   1093      * Share all contacts that are currently selected. This method is pretty inefficient for
   1094      * handling large numbers of contacts. I don't expect this to be a problem.
   1095      */
   1096     private void shareSelectedContacts() {
   1097         final StringBuilder uriListBuilder = new StringBuilder();
   1098         for (Long contactId : getSelectedContactIds()) {
   1099             final Uri contactUri = ContentUris.withAppendedId(
   1100                     ContactsContract.Contacts.CONTENT_URI, contactId);
   1101             final Uri lookupUri = ContactsContract.Contacts.getLookupUri(
   1102                     getContext().getContentResolver(), contactUri);
   1103             if (lookupUri == null) {
   1104                 continue;
   1105             }
   1106             final List<String> pathSegments = lookupUri.getPathSegments();
   1107             if (pathSegments.size() < 2) {
   1108                 continue;
   1109             }
   1110             final String lookupKey = pathSegments.get(pathSegments.size() - 2);
   1111             if (uriListBuilder.length() > 0) {
   1112                 uriListBuilder.append(':');
   1113             }
   1114             uriListBuilder.append(Uri.encode(lookupKey));
   1115         }
   1116         if (uriListBuilder.length() == 0) {
   1117             return;
   1118         }
   1119         final Uri uri = Uri.withAppendedPath(
   1120                 ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI,
   1121                 Uri.encode(uriListBuilder.toString()));
   1122         final Intent intent = new Intent(Intent.ACTION_SEND);
   1123         intent.setType(ContactsContract.Contacts.CONTENT_VCARD_TYPE);
   1124         intent.putExtra(Intent.EXTRA_STREAM, uri);
   1125         try {
   1126             startActivityForResult(Intent.createChooser(intent, getResources().getQuantityString(
   1127                     R.plurals.title_share_via,/* quantity */ getSelectedContactIds().size()))
   1128                     , ACTIVITY_REQUEST_CODE_SHARE);
   1129         } catch (final ActivityNotFoundException ex) {
   1130             Toast.makeText(getContext(), R.string.share_error, Toast.LENGTH_SHORT).show();
   1131         }
   1132     }
   1133 
   1134     private void joinSelectedContacts() {
   1135         final Context context = getContext();
   1136         final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(
   1137                 context, getSelectedContactIdsArray());
   1138         context.startService(intent);
   1139 
   1140         mActionBarAdapter.setSelectionMode(false);
   1141     }
   1142 
   1143     private void deleteSelectedContacts() {
   1144         final ContactMultiDeletionInteraction multiDeletionInteraction =
   1145                 ContactMultiDeletionInteraction.start(this, getSelectedContactIds());
   1146         multiDeletionInteraction.setListener(new MultiDeleteListener());
   1147         mIsDeletionInProgress = true;
   1148     }
   1149 
   1150     private final class MultiDeleteListener implements MultiContactDeleteListener {
   1151         @Override
   1152         public void onDeletionFinished() {
   1153             // The parameters count and numSelected are both the number of contacts before deletion.
   1154             Logger.logListEvent(ListEvent.ActionType.DELETE,
   1155                 /* listType */ getListTypeIncludingSearch(),
   1156                 /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
   1157                 /* numSelected */ getSelectedContactIds().size());
   1158             mActionBarAdapter.setSelectionMode(false);
   1159             mIsDeletionInProgress = false;
   1160         }
   1161     }
   1162 
   1163     private int getListTypeIncludingSearch() {
   1164         return isSearchMode() ? ListEvent.ListType.SEARCH_RESULT : getListType();
   1165     }
   1166 
   1167     public void setParameters(ContactsRequest contactsRequest, boolean fromOnNewIntent) {
   1168         mContactsRequest = contactsRequest;
   1169         mFromOnNewIntent = fromOnNewIntent;
   1170     }
   1171 
   1172     @Override
   1173     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1174         switch (requestCode) {
   1175             // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
   1176             // anymore
   1177             case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
   1178                 if (resultCode == Activity.RESULT_OK) {
   1179                     onPickerResult(data);
   1180                 }
   1181             case ACTIVITY_REQUEST_CODE_SHARE:
   1182                 Logger.logListEvent(ListEvent.ActionType.SHARE,
   1183                     /* listType */ getListTypeIncludingSearch(),
   1184                     /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
   1185                     /* numSelected */ getAdapter().getSelectedContactIds().size());
   1186 
   1187 // TODO fix or remove multipicker code: ag/54762
   1188 //                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
   1189 //                    // Finish the activity if the sub activity was canceled as back key is used
   1190 //                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
   1191 //                    finish();
   1192 //                }
   1193 //                break;
   1194         }
   1195     }
   1196 
   1197     public boolean getOptionsMenuContactsAvailable() {
   1198         return mOptionsMenuContactsAvailable;
   1199     }
   1200 
   1201     @Override
   1202     public void onSaveInstanceState(Bundle outState) {
   1203         super.onSaveInstanceState(outState);
   1204         // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
   1205         // in order to avoid doing fragment transactions after it.
   1206         // TODO Figure out a better way to deal with the issue (ag/120686).
   1207         if (mActionBarAdapter != null) {
   1208             mActionBarAdapter.setListener(null);
   1209             mActionBarAdapter.onSaveInstanceState(outState);
   1210         }
   1211         mDisableOptionItemSelected = true;
   1212         outState.putBoolean(KEY_DELETION_IN_PROGRESS, mIsDeletionInProgress);
   1213         outState.putBoolean(KEY_SEARCH_RESULT_CLICKED, mSearchResultClicked);
   1214     }
   1215 
   1216     @Override
   1217     public void onPause() {
   1218         mOptionsMenuContactsAvailable = false;
   1219         super.onPause();
   1220     }
   1221 
   1222     @Override
   1223     public void onDestroy() {
   1224         if (mActionBarAdapter != null) {
   1225             mActionBarAdapter.setListener(null);
   1226         }
   1227         super.onDestroy();
   1228     }
   1229 
   1230     public boolean onKeyDown(int unicodeChar) {
   1231         if (mActionBarAdapter != null && mActionBarAdapter.isSelectionMode()) {
   1232             // Ignore keyboard input when in selection mode.
   1233             return true;
   1234         }
   1235 
   1236         if (mActionBarAdapter != null && !mActionBarAdapter.isSearchMode()) {
   1237             final String query = new String(new int[]{unicodeChar}, 0, 1);
   1238             mActionBarAdapter.setSearchMode(true);
   1239             mActionBarAdapter.setQueryString(query);
   1240             return true;
   1241         }
   1242 
   1243         return false;
   1244     }
   1245 
   1246     public boolean canSetActionBar() {
   1247         return mCanSetActionBar;
   1248     }
   1249 }
   1250