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