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 androidx.core.content.ContextCompat;
40 import androidx.swiperefreshlayout.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 
DefaultContactBrowseListFragment()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      */
wasSearchResultClicked()261     public boolean wasSearchResultClicked() {
262         return mSearchResultClicked;
263     }
264 
265     /**
266      * Resets whether a search result was clicked by the user to false.
267      */
resetSearchResultClicked()268     public void resetSearchResultClicked() {
269         mSearchResultClicked = false;
270     }
271 
272     @Override
createCursorLoader(Context context)273     public CursorLoader createCursorLoader(Context context) {
274         return new FavoritesAndContactsLoader(context);
275     }
276 
277     @Override
onLoadFinished(Loader<Cursor> loader, Cursor data)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 
maybeShowHamburgerFeatureHighlight()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.
isTalkbackOnAndOnPreLollipopMr1()304     private boolean isTalkbackOnAndOnPreLollipopMr1(){
305         return ((AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE))
306                 .isTouchExplorationEnabled()
307                     && !CompatUtils.isLollipopMr1Compatible();
308     }
309 
bindListHeader(int numberOfContacts)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      */
shouldShowEmptyView(ContactListFilter filter)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.
makeViewVisible(View view)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 
scrollToTop()378     public void scrollToTop() {
379         if (getListView() != null) {
380             getListView().setSelection(0);
381         }
382     }
383 
384     @Override
onItemClick(int position, long id)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
createListAdapter()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
getFilter()413     public ContactListFilter getFilter() {
414         return mContactListFilterController.getFilter();
415     }
416 
417     @Override
inflateView(LayoutInflater inflater, ViewGroup container)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 
getEmptyHomeView(LayoutInflater inflater)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 
getEmptyAccountView(LayoutInflater inflater)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
onCreate(Bundle savedState)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
onCreateView(LayoutInflater inflater, ViewGroup container)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 
turnSyncOn()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
onEnableAutoSync(ContactListFilter filter)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 
dismiss()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 
initSwipeRefreshLayout()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      */
syncContacts(ContactListFilter filter)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 
setSyncOffAlert()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 
setSyncOffMsg(int reason)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
onActivityCreated(Bundle savedInstanceState)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 
initializeActionBarAdapter(Bundle savedInstanceState)686     public void initializeActionBarAdapter(Bundle savedInstanceState) {
687         if (mActionBarAdapter != null) {
688             mActionBarAdapter.initialize(savedInstanceState, mContactsRequest);
689         }
690     }
691 
configureFragment()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 
configureFragmentForRequest()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 
configureContactListFragmentForRequest()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 
setDirectorySearchMode()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
onResume()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 
maybeHideCheckBoxes()774     private void maybeHideCheckBoxes() {
775         if (!mActionBarAdapter.isSelectionMode()) {
776             displayCheckBoxes(false);
777         }
778     }
779 
getActionBarAdapter()780     public ActionBarAdapter getActionBarAdapter(){
781         return mActionBarAdapter;
782     }
783 
784     @Override
setSearchMode(boolean flag)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. */
showSearchProgress(boolean show)792     private void showSearchProgress(boolean show) {
793         if (mSearchProgress != null) {
794             mSearchProgress.setVisibility(show ? View.VISIBLE : View.GONE);
795         }
796     }
797 
checkHeaderViewVisibility()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
setListHeader()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 
getSwipeRefreshLayout()833     public SwipeRefreshLayout getSwipeRefreshLayout() {
834         return mSwipeRefreshLayout;
835     }
836 
837     private final class CheckBoxListListener implements OnCheckBoxListActionListener {
838         @Override
onStartDisplayingCheckBoxes()839         public void onStartDisplayingCheckBoxes() {
840             mActionBarAdapter.setSelectionMode(true);
841             mActivity.invalidateOptionsMenu();
842         }
843 
844         @Override
onSelectedContactIdsChanged()845         public void onSelectedContactIdsChanged() {
846             mActionBarAdapter.setSelectionCount(getSelectedContactIds().size());
847             mActivity.invalidateOptionsMenu();
848             mActionBarAdapter.updateOverflowButtonColor();
849         }
850 
851         @Override
onStopDisplayingCheckBoxes()852         public void onStopDisplayingCheckBoxes() {
853             mActionBarAdapter.setSelectionMode(false);
854         }
855     }
856 
setFilterAndUpdateTitle(ContactListFilter filter)857     public void setFilterAndUpdateTitle(ContactListFilter filter) {
858         setFilterAndUpdateTitle(filter, true);
859     }
860 
setFilterAndUpdateTitle(ContactListFilter filter, boolean restoreSelectedUri)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 
setSwipeRefreshLayoutEnabledOrNot(ContactListFilter filter)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 
configureContactListFragment()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 
getScrollBarPosition()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 {
ContactBrowserActionListener()911         ContactBrowserActionListener() {}
912 
913         @Override
onSelectionChange()914         public void onSelectionChange() {
915         }
916 
917         @Override
onViewContactAction(int position, Uri contactLookupUri, boolean isEnterpriseContact)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
onDeleteContactAction(Uri contactUri)951         public void onDeleteContactAction(Uri contactUri) {
952             ContactDeletionInteraction.start(mActivity, contactUri, false);
953         }
954 
955         @Override
onFinishAction()956         public void onFinishAction() {
957             mActivity.onBackPressed();
958         }
959 
960         @Override
onInvalidSelection()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 
isAllContactsFilter(ContactListFilter filter)977     private boolean isAllContactsFilter(ContactListFilter filter) {
978         return filter != null && filter.isContactsFilterType();
979     }
980 
setContactsAvailable(boolean contactsAvailable)981     public void setContactsAvailable(boolean contactsAvailable) {
982         mContactsAvailable = contactsAvailable;
983     }
984 
985     /**
986      * Set filter via ContactListFilterController
987      */
setContactListFilter(ContactListFilter filter)988     private void setContactListFilter(ContactListFilter filter) {
989         mContactListFilterController.setContactListFilter(filter,
990                 /* persistent */ isAllContactsFilter(filter));
991     }
992 
993     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)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
onPrepareOptionsMenu(Menu menu)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 
makeMenuItemVisible(Menu menu, int itemId, boolean visible)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 
hasExportIntentHandler()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
onOptionsItemSelected(MenuItem item)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      */
shareSelectedContacts()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 
joinSelectedContacts()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 
deleteSelectedContacts()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
onDeletionFinished()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 
getListTypeIncludingSearch()1163     private int getListTypeIncludingSearch() {
1164         return isSearchMode() ? ListEvent.ListType.SEARCH_RESULT : getListType();
1165     }
1166 
setParameters(ContactsRequest contactsRequest, boolean fromOnNewIntent)1167     public void setParameters(ContactsRequest contactsRequest, boolean fromOnNewIntent) {
1168         mContactsRequest = contactsRequest;
1169         mFromOnNewIntent = fromOnNewIntent;
1170     }
1171 
1172     @Override
onActivityResult(int requestCode, int resultCode, Intent data)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 
getOptionsMenuContactsAvailable()1197     public boolean getOptionsMenuContactsAvailable() {
1198         return mOptionsMenuContactsAvailable;
1199     }
1200 
1201     @Override
onSaveInstanceState(Bundle outState)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
onPause()1217     public void onPause() {
1218         mOptionsMenuContactsAvailable = false;
1219         super.onPause();
1220     }
1221 
1222     @Override
onDestroy()1223     public void onDestroy() {
1224         if (mActionBarAdapter != null) {
1225             mActionBarAdapter.setListener(null);
1226         }
1227         super.onDestroy();
1228     }
1229 
onKeyDown(int unicodeChar)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 
canSetActionBar()1246     public boolean canSetActionBar() {
1247         return mCanSetActionBar;
1248     }
1249 }
1250