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