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 
17 package com.android.contacts.activities;
18 
19 import android.app.Fragment;
20 import android.app.FragmentManager;
21 import android.app.FragmentTransaction;
22 import android.content.ActivityNotFoundException;
23 import android.content.ContentUris;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.graphics.Rect;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Parcelable;
32 import android.provider.ContactsContract;
33 import android.provider.ContactsContract.Contacts;
34 import android.provider.ContactsContract.ProviderStatus;
35 import android.provider.ContactsContract.QuickContact;
36 import android.provider.Settings;
37 import android.support.v13.app.FragmentPagerAdapter;
38 import android.support.v4.view.PagerAdapter;
39 import android.support.v4.view.ViewPager;
40 import android.telecom.TelecomManager;
41 import android.text.TextUtils;
42 import android.util.Log;
43 import android.view.KeyCharacterMap;
44 import android.view.KeyEvent;
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.Window;
51 import android.widget.ImageButton;
52 import android.widget.Toast;
53 import android.widget.Toolbar;
54 
55 import com.android.contacts.ContactsActivity;
56 import com.android.contacts.R;
57 import com.android.contacts.activities.ActionBarAdapter.TabState;
58 import com.android.contacts.common.ContactsUtils;
59 import com.android.contacts.common.activity.RequestPermissionsActivity;
60 import com.android.contacts.common.compat.TelecomManagerUtil;
61 import com.android.contacts.common.compat.BlockedNumberContractCompat;
62 import com.android.contacts.common.dialog.ClearFrequentsDialog;
63 import com.android.contacts.common.interactions.ImportExportDialogFragment;
64 import com.android.contacts.common.list.ContactEntryListFragment;
65 import com.android.contacts.common.list.ContactListFilter;
66 import com.android.contacts.common.list.ContactListFilterController;
67 import com.android.contacts.common.list.ContactTileAdapter.DisplayType;
68 import com.android.contacts.common.list.DirectoryListLoader;
69 import com.android.contacts.common.list.ViewPagerTabs;
70 import com.android.contacts.common.logging.Logger;
71 import com.android.contacts.common.logging.ScreenEvent.ScreenType;
72 import com.android.contacts.common.preference.ContactsPreferenceActivity;
73 import com.android.contacts.common.util.AccountFilterUtil;
74 import com.android.contacts.common.util.Constants;
75 import com.android.contacts.common.util.ImplicitIntentsUtil;
76 import com.android.contacts.common.util.ViewUtil;
77 import com.android.contacts.common.widget.FloatingActionButtonController;
78 import com.android.contacts.editor.EditorIntents;
79 import com.android.contacts.interactions.ContactDeletionInteraction;
80 import com.android.contacts.interactions.ContactMultiDeletionInteraction;
81 import com.android.contacts.interactions.ContactMultiDeletionInteraction.MultiContactDeleteListener;
82 import com.android.contacts.interactions.JoinContactsDialogFragment;
83 import com.android.contacts.interactions.JoinContactsDialogFragment.JoinContactsListener;
84 import com.android.contacts.list.ContactTileListFragment;
85 import com.android.contacts.list.ContactsIntentResolver;
86 import com.android.contacts.list.ContactsRequest;
87 import com.android.contacts.list.ContactsUnavailableFragment;
88 import com.android.contacts.list.MultiSelectContactsListFragment;
89 import com.android.contacts.list.MultiSelectContactsListFragment.OnCheckBoxListActionListener;
90 import com.android.contacts.list.OnContactBrowserActionListener;
91 import com.android.contacts.list.OnContactsUnavailableActionListener;
92 import com.android.contacts.list.ProviderStatusWatcher;
93 import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
94 import com.android.contacts.quickcontact.QuickContactActivity;
95 import com.android.contacts.util.DialogManager;
96 import com.android.contacts.util.PhoneCapabilityTester;
97 import com.android.contactsbind.HelpUtils;
98 
99 import java.util.List;
100 import java.util.Locale;
101 import java.util.concurrent.atomic.AtomicInteger;
102 
103 /**
104  * Displays a list to browse contacts.
105  */
106 public class PeopleActivity extends ContactsActivity implements
107         View.OnCreateContextMenuListener,
108         View.OnClickListener,
109         ActionBarAdapter.Listener,
110         DialogManager.DialogShowingViewActivity,
111         ContactListFilterController.ContactListFilterListener,
112         ProviderStatusListener,
113         MultiContactDeleteListener,
114         JoinContactsListener {
115 
116     private static final String TAG = "PeopleActivity";
117 
118     private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
119 
120     // These values needs to start at 2. See {@link ContactEntryListFragment}.
121     private static final int SUBACTIVITY_ACCOUNT_FILTER = 2;
122 
123     private final DialogManager mDialogManager = new DialogManager(this);
124 
125     private ContactsIntentResolver mIntentResolver;
126     private ContactsRequest mRequest;
127 
128     private ActionBarAdapter mActionBarAdapter;
129     private FloatingActionButtonController mFloatingActionButtonController;
130     private View mFloatingActionButtonContainer;
131     private boolean wasLastFabAnimationScaleIn = false;
132 
133     private ContactTileListFragment.Listener mFavoritesFragmentListener =
134             new StrequentContactListFragmentListener();
135 
136     private ContactListFilterController mContactListFilterController;
137 
138     private ContactsUnavailableFragment mContactsUnavailableFragment;
139     private ProviderStatusWatcher mProviderStatusWatcher;
140     private Integer mProviderStatus;
141 
142     private boolean mOptionsMenuContactsAvailable;
143 
144     /**
145      * Showing a list of Contacts. Also used for showing search results in search mode.
146      */
147     private MultiSelectContactsListFragment mAllFragment;
148     private ContactTileListFragment mFavoritesFragment;
149 
150     /** ViewPager for swipe */
151     private ViewPager mTabPager;
152     private ViewPagerTabs mViewPagerTabs;
153     private TabPagerAdapter mTabPagerAdapter;
154     private String[] mTabTitles;
155     private final TabPagerListener mTabPagerListener = new TabPagerListener();
156 
157     private boolean mEnableDebugMenuOptions;
158 
159     /**
160      * True if this activity instance is a re-created one.  i.e. set true after orientation change.
161      * This is set in {@link #onCreate} for later use in {@link #onStart}.
162      */
163     private boolean mIsRecreatedInstance;
164 
165     /**
166      * If {@link #configureFragments(boolean)} is already called.  Used to avoid calling it twice
167      * in {@link #onStart}.
168      * (This initialization only needs to be done once in onStart() when the Activity was just
169      * created from scratch -- i.e. onCreate() was just called)
170      */
171     private boolean mFragmentInitialized;
172 
173     /**
174      * This is to disable {@link #onOptionsItemSelected} when we trying to stop the activity.
175      */
176     private boolean mDisableOptionItemSelected;
177 
178     /** Sequential ID assigned to each instance; used for logging */
179     private final int mInstanceId;
180     private static final AtomicInteger sNextInstanceId = new AtomicInteger();
181 
PeopleActivity()182     public PeopleActivity() {
183         mInstanceId = sNextInstanceId.getAndIncrement();
184         mIntentResolver = new ContactsIntentResolver(this);
185         mProviderStatusWatcher = ProviderStatusWatcher.getInstance(this);
186     }
187 
188     @Override
toString()189     public String toString() {
190         // Shown on logcat
191         return String.format("%s@%d", getClass().getSimpleName(), mInstanceId);
192     }
193 
areContactsAvailable()194     public boolean areContactsAvailable() {
195         return (mProviderStatus != null) && mProviderStatus.equals(ProviderStatus.STATUS_NORMAL);
196     }
197 
areGroupWritableAccountsAvailable()198     private boolean areGroupWritableAccountsAvailable() {
199         return ContactsUtils.areGroupWritableAccountsAvailable(this);
200     }
201 
202     /**
203      * Initialize fragments that are (or may not be) in the layout.
204      *
205      * For the fragments that are in the layout, we initialize them in
206      * {@link #createViewsAndFragments(Bundle)} after inflating the layout.
207      *
208      * However, the {@link ContactsUnavailableFragment} is a special fragment which may not
209      * be in the layout, so we have to do the initialization here.
210      *
211      * The ContactsUnavailableFragment is always created at runtime.
212      */
213     @Override
onAttachFragment(Fragment fragment)214     public void onAttachFragment(Fragment fragment) {
215         if (fragment instanceof ContactsUnavailableFragment) {
216             mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment;
217             mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
218                     new ContactsUnavailableFragmentListener());
219         }
220     }
221 
222     @Override
onCreate(Bundle savedState)223     protected void onCreate(Bundle savedState) {
224         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
225             Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start");
226         }
227         super.onCreate(savedState);
228 
229         if (RequestPermissionsActivity.startPermissionActivity(this)) {
230             return;
231         }
232 
233         if (!processIntent(false)) {
234             finish();
235             return;
236         }
237         mContactListFilterController = ContactListFilterController.getInstance(this);
238         mContactListFilterController.checkFilterValidity(false);
239         mContactListFilterController.addListener(this);
240 
241         mProviderStatusWatcher.addListener(this);
242 
243         mIsRecreatedInstance = (savedState != null);
244         createViewsAndFragments(savedState);
245 
246         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
247             Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish");
248         }
249         getWindow().setBackgroundDrawable(null);
250     }
251 
252     @Override
onNewIntent(Intent intent)253     protected void onNewIntent(Intent intent) {
254         setIntent(intent);
255         if (!processIntent(true)) {
256             finish();
257             return;
258         }
259         mActionBarAdapter.initialize(null, mRequest);
260 
261         mContactListFilterController.checkFilterValidity(false);
262 
263         // Re-configure fragments.
264         configureFragments(true /* from request */);
265         initializeFabVisibility();
266         invalidateOptionsMenuIfNeeded();
267     }
268 
269     /**
270      * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect
271      * is needed.
272      *
273      * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}.
274      * @return {@code true} if {@link PeopleActivity} should continue running.  {@code false}
275      *         if it shouldn't, in which case the caller should finish() itself and shouldn't do
276      *         farther initialization.
277      */
processIntent(boolean forNewIntent)278     private boolean processIntent(boolean forNewIntent) {
279         // Extract relevant information from the intent
280         mRequest = mIntentResolver.resolveIntent(getIntent());
281         if (Log.isLoggable(TAG, Log.DEBUG)) {
282             Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent
283                     + " intent=" + getIntent() + " request=" + mRequest);
284         }
285         if (!mRequest.isValid()) {
286             setResult(RESULT_CANCELED);
287             return false;
288         }
289 
290         if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT) {
291             final Intent intent = ImplicitIntentsUtil.composeQuickContactIntent(
292                     mRequest.getContactUri(), QuickContactActivity.MODE_FULLY_EXPANDED);
293             intent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.UNKNOWN);
294             ImplicitIntentsUtil.startActivityInApp(this, intent);
295             return false;
296         }
297         return true;
298     }
299 
createViewsAndFragments(Bundle savedState)300     private void createViewsAndFragments(Bundle savedState) {
301         // Disable the ActionBar so that we can use a Toolbar. This needs to be called before
302         // setContentView().
303         getWindow().requestFeature(Window.FEATURE_NO_TITLE);
304 
305         setContentView(R.layout.people_activity);
306 
307         final FragmentManager fragmentManager = getFragmentManager();
308 
309         // Hide all tabs (the current tab will later be reshown once a tab is selected)
310         final FragmentTransaction transaction = fragmentManager.beginTransaction();
311 
312         mTabTitles = new String[TabState.COUNT];
313         mTabTitles[TabState.FAVORITES] = getString(R.string.favorites_tab_label);
314         mTabTitles[TabState.ALL] = getString(R.string.all_contacts_tab_label);
315         mTabPager = getView(R.id.tab_pager);
316         mTabPagerAdapter = new TabPagerAdapter();
317         mTabPager.setAdapter(mTabPagerAdapter);
318         mTabPager.setOnPageChangeListener(mTabPagerListener);
319 
320         // Configure toolbar and toolbar tabs. If in landscape mode, we  configure tabs differntly.
321         final Toolbar toolbar = getView(R.id.toolbar);
322         setActionBar(toolbar);
323         final ViewPagerTabs portraitViewPagerTabs
324                 = (ViewPagerTabs) findViewById(R.id.lists_pager_header);
325         ViewPagerTabs landscapeViewPagerTabs = null;
326         if (portraitViewPagerTabs ==  null) {
327             landscapeViewPagerTabs = (ViewPagerTabs) getLayoutInflater().inflate(
328                     R.layout.people_activity_tabs_lands, toolbar, /* attachToRoot = */ false);
329             mViewPagerTabs = landscapeViewPagerTabs;
330         } else {
331             mViewPagerTabs = portraitViewPagerTabs;
332         }
333         mViewPagerTabs.setViewPager(mTabPager);
334 
335         final String FAVORITE_TAG = "tab-pager-favorite";
336         final String ALL_TAG = "tab-pager-all";
337 
338         // Create the fragments and add as children of the view pager.
339         // The pager adapter will only change the visibility; it'll never create/destroy
340         // fragments.
341         // However, if it's after screen rotation, the fragments have been re-created by
342         // the fragment manager, so first see if there're already the target fragments
343         // existing.
344         mFavoritesFragment = (ContactTileListFragment)
345                 fragmentManager.findFragmentByTag(FAVORITE_TAG);
346         mAllFragment = (MultiSelectContactsListFragment)
347                 fragmentManager.findFragmentByTag(ALL_TAG);
348 
349         if (mFavoritesFragment == null) {
350             mFavoritesFragment = new ContactTileListFragment();
351             mAllFragment = new MultiSelectContactsListFragment();
352 
353             transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG);
354             transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG);
355         }
356 
357         mFavoritesFragment.setListener(mFavoritesFragmentListener);
358 
359         mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener());
360         mAllFragment.setCheckBoxListListener(new CheckBoxListListener());
361 
362         // Hide all fragments for now.  We adjust visibility when we get onSelectedTabChanged()
363         // from ActionBarAdapter.
364         transaction.hide(mFavoritesFragment);
365         transaction.hide(mAllFragment);
366 
367         transaction.commitAllowingStateLoss();
368         fragmentManager.executePendingTransactions();
369 
370         // Setting Properties after fragment is created
371         mFavoritesFragment.setDisplayType(DisplayType.STREQUENT);
372 
373         mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar(),
374                 portraitViewPagerTabs, landscapeViewPagerTabs, toolbar);
375         mActionBarAdapter.initialize(savedState, mRequest);
376 
377         // Add shadow under toolbar
378         ViewUtil.addRectangularOutlineProvider(findViewById(R.id.toolbar_parent), getResources());
379 
380         // Configure floating action button
381         mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
382         final ImageButton floatingActionButton
383                 = (ImageButton) findViewById(R.id.floating_action_button);
384         floatingActionButton.setOnClickListener(this);
385         mFloatingActionButtonController = new FloatingActionButtonController(this,
386                 mFloatingActionButtonContainer, floatingActionButton);
387         initializeFabVisibility();
388 
389         invalidateOptionsMenuIfNeeded();
390     }
391 
392     @Override
onStart()393     protected void onStart() {
394         if (!mFragmentInitialized) {
395             mFragmentInitialized = true;
396             /* Configure fragments if we haven't.
397              *
398              * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}.
399              *
400              * However, because this method may indirectly touch views in fragments but fragments
401              * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT
402              * have views until {@link Activity#onCreate} finishes (they would if they were inflated
403              * from a layout), we need to do it here in {@link #onStart()}.
404              *
405              * (When {@link Fragment#onCreateView} is called is different in the former case and
406              * in the latter case, unfortunately.)
407              *
408              * Also, we skip most of the work in it if the activity is a re-created one.
409              * (so the argument.)
410              */
411             configureFragments(!mIsRecreatedInstance);
412         }
413         super.onStart();
414     }
415 
416     @Override
onPause()417     protected void onPause() {
418         mOptionsMenuContactsAvailable = false;
419         mProviderStatusWatcher.stop();
420         super.onPause();
421     }
422 
423     @Override
onResume()424     protected void onResume() {
425         super.onResume();
426 
427         mProviderStatusWatcher.start();
428         updateViewConfiguration(true);
429 
430         // Re-register the listener, which may have been cleared when onSaveInstanceState was
431         // called.  See also: onSaveInstanceState
432         mActionBarAdapter.setListener(this);
433         mDisableOptionItemSelected = false;
434         if (mTabPager != null) {
435             mTabPager.setOnPageChangeListener(mTabPagerListener);
436         }
437         // Current tab may have changed since the last onSaveInstanceState().  Make sure
438         // the actual contents match the tab.
439         updateFragmentsVisibility();
440     }
441 
442     @Override
onDestroy()443     protected void onDestroy() {
444         mProviderStatusWatcher.removeListener(this);
445 
446         // Some of variables will be null if this Activity redirects Intent.
447         // See also onCreate() or other methods called during the Activity's initialization.
448         if (mActionBarAdapter != null) {
449             mActionBarAdapter.setListener(null);
450         }
451         if (mContactListFilterController != null) {
452             mContactListFilterController.removeListener(this);
453         }
454 
455         super.onDestroy();
456     }
457 
configureFragments(boolean fromRequest)458     private void configureFragments(boolean fromRequest) {
459         if (fromRequest) {
460             ContactListFilter filter = null;
461             int actionCode = mRequest.getActionCode();
462             boolean searchMode = mRequest.isSearchMode();
463             final int tabToOpen;
464             switch (actionCode) {
465                 case ContactsRequest.ACTION_ALL_CONTACTS:
466                     filter = ContactListFilter.createFilterWithType(
467                             ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
468                     tabToOpen = TabState.ALL;
469                     break;
470                 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
471                     filter = ContactListFilter.createFilterWithType(
472                             ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
473                     tabToOpen = TabState.ALL;
474                     break;
475 
476                 case ContactsRequest.ACTION_FREQUENT:
477                 case ContactsRequest.ACTION_STREQUENT:
478                 case ContactsRequest.ACTION_STARRED:
479                     tabToOpen = TabState.FAVORITES;
480                     break;
481                 case ContactsRequest.ACTION_VIEW_CONTACT:
482                     tabToOpen = TabState.ALL;
483                     break;
484                 default:
485                     tabToOpen = -1;
486                     break;
487             }
488             if (tabToOpen != -1) {
489                 mActionBarAdapter.setCurrentTab(tabToOpen);
490             }
491 
492             if (filter != null) {
493                 mContactListFilterController.setContactListFilter(filter, false);
494                 searchMode = false;
495             }
496 
497             if (mRequest.getContactUri() != null) {
498                 searchMode = false;
499             }
500 
501             mActionBarAdapter.setSearchMode(searchMode);
502             configureContactListFragmentForRequest();
503         }
504 
505         configureContactListFragment();
506 
507         invalidateOptionsMenuIfNeeded();
508     }
509 
initializeFabVisibility()510     private void initializeFabVisibility() {
511         final boolean hideFab = mActionBarAdapter.isSearchMode()
512                 || mActionBarAdapter.isSelectionMode();
513         mFloatingActionButtonContainer.setVisibility(hideFab ? View.GONE : View.VISIBLE);
514         mFloatingActionButtonController.resetIn();
515         wasLastFabAnimationScaleIn = !hideFab;
516     }
517 
showFabWithAnimation(boolean showFab)518     private void showFabWithAnimation(boolean showFab) {
519         if (mFloatingActionButtonContainer == null) {
520             return;
521         }
522         if (showFab) {
523             if (!wasLastFabAnimationScaleIn) {
524                 mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
525                 mFloatingActionButtonController.scaleIn(0);
526             }
527             wasLastFabAnimationScaleIn = true;
528 
529         } else {
530             if (wasLastFabAnimationScaleIn) {
531                 mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
532                 mFloatingActionButtonController.scaleOut();
533             }
534             wasLastFabAnimationScaleIn = false;
535         }
536     }
537 
538     @Override
onContactListFilterChanged()539     public void onContactListFilterChanged() {
540         if (mAllFragment == null || !mAllFragment.isAdded()) {
541             return;
542         }
543 
544         mAllFragment.setFilter(mContactListFilterController.getFilter());
545 
546         invalidateOptionsMenuIfNeeded();
547     }
548 
549     /**
550      * Handler for action bar actions.
551      */
552     @Override
onAction(int action)553     public void onAction(int action) {
554         switch (action) {
555             case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
556                 mAllFragment.displayCheckBoxes(true);
557                 startSearchOrSelectionMode();
558                 break;
559             case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
560                 if (!mIsRecreatedInstance) {
561                     Logger.logScreenView(this, ScreenType.SEARCH);
562                 }
563                 startSearchOrSelectionMode();
564                 break;
565             case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE:
566                 showFabWithAnimation(/* showFabWithAnimation = */ true);
567                 break;
568             case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
569                 setQueryTextToFragment("");
570                 updateFragmentsVisibility();
571                 invalidateOptionsMenu();
572                 showFabWithAnimation(/* showFabWithAnimation = */ true);
573                 break;
574             case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
575                 final String queryString = mActionBarAdapter.getQueryString();
576                 setQueryTextToFragment(queryString);
577                 updateDebugOptionsVisibility(
578                         ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
579                 break;
580             default:
581                 throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action);
582         }
583     }
584 
startSearchOrSelectionMode()585     private void startSearchOrSelectionMode() {
586         configureFragments(false /* from request */);
587         updateFragmentsVisibility();
588         invalidateOptionsMenu();
589         showFabWithAnimation(/* showFabWithAnimation = */ false);
590     }
591 
592     @Override
onSelectedTabChanged()593     public void onSelectedTabChanged() {
594         updateFragmentsVisibility();
595     }
596 
597     @Override
onUpButtonPressed()598     public void onUpButtonPressed() {
599         onBackPressed();
600     }
601 
updateDebugOptionsVisibility(boolean visible)602     private void updateDebugOptionsVisibility(boolean visible) {
603         if (mEnableDebugMenuOptions != visible) {
604             mEnableDebugMenuOptions = visible;
605             invalidateOptionsMenu();
606         }
607     }
608 
609     /**
610      * Updates the fragment/view visibility according to the current mode, such as
611      * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}.
612      */
updateFragmentsVisibility()613     private void updateFragmentsVisibility() {
614         int tab = mActionBarAdapter.getCurrentTab();
615 
616         if (mActionBarAdapter.isSearchMode() || mActionBarAdapter.isSelectionMode()) {
617             mTabPagerAdapter.setTabsHidden(true);
618         } else {
619             // No smooth scrolling if quitting from the search/selection mode.
620             final boolean wereTabsHidden = mTabPagerAdapter.areTabsHidden()
621                     || mActionBarAdapter.isSelectionMode();
622             mTabPagerAdapter.setTabsHidden(false);
623             if (mTabPager.getCurrentItem() != tab) {
624                 mTabPager.setCurrentItem(tab, !wereTabsHidden);
625             }
626         }
627         if (!mActionBarAdapter.isSelectionMode()) {
628             mAllFragment.displayCheckBoxes(false);
629         }
630         invalidateOptionsMenu();
631         showEmptyStateForTab(tab);
632     }
633 
showEmptyStateForTab(int tab)634     private void showEmptyStateForTab(int tab) {
635         if (mContactsUnavailableFragment != null) {
636             switch (getTabPositionForTextDirection(tab)) {
637                 case TabState.FAVORITES:
638                     mContactsUnavailableFragment.setTabInfo(
639                             R.string.listTotalAllContactsZeroStarred, TabState.FAVORITES);
640                     break;
641                 case TabState.ALL:
642                     mContactsUnavailableFragment.setTabInfo(R.string.noContacts, TabState.ALL);
643                     break;
644             }
645             // When using the mContactsUnavailableFragment the ViewPager doesn't contain two views.
646             // Therefore, we have to trick the ViewPagerTabs into thinking we have changed tabs
647             // when the mContactsUnavailableFragment changes. Otherwise the tab strip won't move.
648             mViewPagerTabs.onPageScrolled(tab, 0, 0);
649         }
650     }
651 
652     private class TabPagerListener implements ViewPager.OnPageChangeListener {
653 
654         // This package-protected constructor is here because of a possible compiler bug.
655         // PeopleActivity$1.class should be generated due to the private outer/inner class access
656         // needed here.  But for some reason, PeopleActivity$1.class is missing.
657         // Since $1 class is needed as a jvm work around to get access to the inner class,
658         // changing the constructor to package-protected or public will solve the problem.
659         // To verify whether $1 class is needed, javap PeopleActivity$TabPagerListener and look for
660         // references to PeopleActivity$1.
661         //
662         // When the constructor is private and PeopleActivity$1.class is missing, proguard will
663         // correctly catch this and throw warnings and error out the build on user/userdebug builds.
664         //
665         // All private inner classes below also need this fix.
TabPagerListener()666         TabPagerListener() {}
667 
668         @Override
onPageScrollStateChanged(int state)669         public void onPageScrollStateChanged(int state) {
670             if (!mTabPagerAdapter.areTabsHidden()) {
671                 mViewPagerTabs.onPageScrollStateChanged(state);
672             }
673         }
674 
675         @Override
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)676         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
677             if (!mTabPagerAdapter.areTabsHidden()) {
678                 mViewPagerTabs.onPageScrolled(position, positionOffset, positionOffsetPixels);
679             }
680         }
681 
682         @Override
onPageSelected(int position)683         public void onPageSelected(int position) {
684             // Make sure not in the search mode, in which case position != TabState.ordinal().
685             if (!mTabPagerAdapter.areTabsHidden()) {
686                 mActionBarAdapter.setCurrentTab(position, false);
687                 mViewPagerTabs.onPageSelected(position);
688                 showEmptyStateForTab(position);
689                 invalidateOptionsMenu();
690             }
691         }
692     }
693 
694     /**
695      * Adapter for the {@link ViewPager}.  Unlike {@link FragmentPagerAdapter},
696      * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/
697      * {@link #destroyItem} show/hide fragments instead of attaching/detaching.
698      *
699      * In search mode, we always show the "all" fragment, and disable the swipe.  We change the
700      * number of items to 1 to disable the swipe.
701      *
702      * TODO figure out a more straight way to disable swipe.
703      */
704     private class TabPagerAdapter extends PagerAdapter {
705         private final FragmentManager mFragmentManager;
706         private FragmentTransaction mCurTransaction = null;
707 
708         private boolean mAreTabsHiddenInTabPager;
709 
710         private Fragment mCurrentPrimaryItem;
711 
TabPagerAdapter()712         public TabPagerAdapter() {
713             mFragmentManager = getFragmentManager();
714         }
715 
areTabsHidden()716         public boolean areTabsHidden() {
717             return mAreTabsHiddenInTabPager;
718         }
719 
setTabsHidden(boolean hideTabs)720         public void setTabsHidden(boolean hideTabs) {
721             if (hideTabs == mAreTabsHiddenInTabPager) {
722                 return;
723             }
724             mAreTabsHiddenInTabPager = hideTabs;
725             notifyDataSetChanged();
726         }
727 
728         @Override
getCount()729         public int getCount() {
730             return mAreTabsHiddenInTabPager ? 1 : TabState.COUNT;
731         }
732 
733         /** Gets called when the number of items changes. */
734         @Override
getItemPosition(Object object)735         public int getItemPosition(Object object) {
736             if (mAreTabsHiddenInTabPager) {
737                 if (object == mAllFragment) {
738                     return 0; // Only 1 page in search mode
739                 }
740             } else {
741                 if (object == mFavoritesFragment) {
742                     return getTabPositionForTextDirection(TabState.FAVORITES);
743                 }
744                 if (object == mAllFragment) {
745                     return getTabPositionForTextDirection(TabState.ALL);
746                 }
747             }
748             return POSITION_NONE;
749         }
750 
751         @Override
startUpdate(ViewGroup container)752         public void startUpdate(ViewGroup container) {
753         }
754 
getFragment(int position)755         private Fragment getFragment(int position) {
756             position = getTabPositionForTextDirection(position);
757             if (mAreTabsHiddenInTabPager) {
758                 if (position != 0) {
759                     // This has only been observed in monkey tests.
760                     // Let's log this issue, but not crash
761                     Log.w(TAG, "Request fragment at position=" + position + ", eventhough we " +
762                             "are in search mode");
763                 }
764                 return mAllFragment;
765             } else {
766                 if (position == TabState.FAVORITES) {
767                     return mFavoritesFragment;
768                 } else if (position == TabState.ALL) {
769                     return mAllFragment;
770                 }
771             }
772             throw new IllegalArgumentException("position: " + position);
773         }
774 
775         @Override
instantiateItem(ViewGroup container, int position)776         public Object instantiateItem(ViewGroup container, int position) {
777             if (mCurTransaction == null) {
778                 mCurTransaction = mFragmentManager.beginTransaction();
779             }
780             Fragment f = getFragment(position);
781             mCurTransaction.show(f);
782 
783             // Non primary pages are not visible.
784             f.setUserVisibleHint(f == mCurrentPrimaryItem);
785             return f;
786         }
787 
788         @Override
destroyItem(ViewGroup container, int position, Object object)789         public void destroyItem(ViewGroup container, int position, Object object) {
790             if (mCurTransaction == null) {
791                 mCurTransaction = mFragmentManager.beginTransaction();
792             }
793             mCurTransaction.hide((Fragment) object);
794         }
795 
796         @Override
finishUpdate(ViewGroup container)797         public void finishUpdate(ViewGroup container) {
798             if (mCurTransaction != null) {
799                 mCurTransaction.commitAllowingStateLoss();
800                 mCurTransaction = null;
801                 mFragmentManager.executePendingTransactions();
802             }
803         }
804 
805         @Override
isViewFromObject(View view, Object object)806         public boolean isViewFromObject(View view, Object object) {
807             return ((Fragment) object).getView() == view;
808         }
809 
810         @Override
setPrimaryItem(ViewGroup container, int position, Object object)811         public void setPrimaryItem(ViewGroup container, int position, Object object) {
812             Fragment fragment = (Fragment) object;
813             if (mCurrentPrimaryItem != fragment) {
814                 if (mCurrentPrimaryItem != null) {
815                     mCurrentPrimaryItem.setUserVisibleHint(false);
816                 }
817                 if (fragment != null) {
818                     fragment.setUserVisibleHint(true);
819                 }
820                 mCurrentPrimaryItem = fragment;
821             }
822         }
823 
824         @Override
saveState()825         public Parcelable saveState() {
826             return null;
827         }
828 
829         @Override
restoreState(Parcelable state, ClassLoader loader)830         public void restoreState(Parcelable state, ClassLoader loader) {
831         }
832 
833         @Override
getPageTitle(int position)834         public CharSequence getPageTitle(int position) {
835             return mTabTitles[position];
836         }
837     }
838 
setQueryTextToFragment(String query)839     private void setQueryTextToFragment(String query) {
840         mAllFragment.setQueryString(query, true);
841         mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode());
842     }
843 
configureContactListFragmentForRequest()844     private void configureContactListFragmentForRequest() {
845         Uri contactUri = mRequest.getContactUri();
846         if (contactUri != null) {
847             mAllFragment.setSelectedContactUri(contactUri);
848         }
849 
850         mAllFragment.setFilter(mContactListFilterController.getFilter());
851         setQueryTextToFragment(mActionBarAdapter.getQueryString());
852 
853         if (mRequest.isDirectorySearchEnabled()) {
854             mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
855         } else {
856             mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
857         }
858     }
859 
configureContactListFragment()860     private void configureContactListFragment() {
861         // Filter may be changed when this Activity is in background.
862         mAllFragment.setFilter(mContactListFilterController.getFilter());
863 
864         mAllFragment.setVerticalScrollbarPosition(getScrollBarPosition());
865         mAllFragment.setSelectionVisible(false);
866     }
867 
getScrollBarPosition()868     private int getScrollBarPosition() {
869         return isRTL() ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
870     }
871 
isRTL()872     private boolean isRTL() {
873         final Locale locale = Locale.getDefault();
874         return TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
875     }
876 
877     @Override
onProviderStatusChange()878     public void onProviderStatusChange() {
879         updateViewConfiguration(false);
880     }
881 
updateViewConfiguration(boolean forceUpdate)882     private void updateViewConfiguration(boolean forceUpdate) {
883         int providerStatus = mProviderStatusWatcher.getProviderStatus();
884         if (!forceUpdate && (mProviderStatus != null)
885                 && (mProviderStatus.equals(providerStatus))) return;
886         mProviderStatus = providerStatus;
887 
888         View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view);
889 
890         if (mProviderStatus.equals(ProviderStatus.STATUS_NORMAL)) {
891             // Ensure that the mTabPager is visible; we may have made it invisible below.
892             contactsUnavailableView.setVisibility(View.GONE);
893             if (mTabPager != null) {
894                 mTabPager.setVisibility(View.VISIBLE);
895             }
896 
897             if (mAllFragment != null) {
898                 mAllFragment.setEnabled(true);
899             }
900         } else {
901             // Setting up the page so that the user can still use the app
902             // even without an account.
903             if (mAllFragment != null) {
904                 mAllFragment.setEnabled(false);
905             }
906             if (mContactsUnavailableFragment == null) {
907                 mContactsUnavailableFragment = new ContactsUnavailableFragment();
908                 mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
909                         new ContactsUnavailableFragmentListener());
910                 getFragmentManager().beginTransaction()
911                         .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment)
912                         .commitAllowingStateLoss();
913             }
914             mContactsUnavailableFragment.updateStatus(mProviderStatus);
915 
916             // Show the contactsUnavailableView, and hide the mTabPager so that we don't
917             // see it sliding in underneath the contactsUnavailableView at the edges.
918             contactsUnavailableView.setVisibility(View.VISIBLE);
919             if (mTabPager != null) {
920                 mTabPager.setVisibility(View.GONE);
921             }
922 
923             showEmptyStateForTab(mActionBarAdapter.getCurrentTab());
924         }
925 
926         invalidateOptionsMenuIfNeeded();
927     }
928 
929     private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
ContactBrowserActionListener()930         ContactBrowserActionListener() {}
931 
932         @Override
onSelectionChange()933         public void onSelectionChange() {
934 
935         }
936 
937         @Override
onViewContactAction(Uri contactLookupUri, boolean isEnterpriseContact)938         public void onViewContactAction(Uri contactLookupUri, boolean isEnterpriseContact) {
939             if (isEnterpriseContact) {
940                 // No implicit intent as user may have a different contacts app in work profile.
941                 QuickContact.showQuickContact(PeopleActivity.this, new Rect(), contactLookupUri,
942                         QuickContactActivity.MODE_FULLY_EXPANDED, null);
943             } else {
944                 final Intent intent = ImplicitIntentsUtil.composeQuickContactIntent(
945                         contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED);
946                 intent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE,
947                         mAllFragment.isSearchMode() ? ScreenType.SEARCH : ScreenType.ALL_CONTACTS);
948                 ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
949             }
950         }
951 
952         @Override
onDeleteContactAction(Uri contactUri)953         public void onDeleteContactAction(Uri contactUri) {
954             ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
955         }
956 
957         @Override
onFinishAction()958         public void onFinishAction() {
959             onBackPressed();
960         }
961 
962         @Override
onInvalidSelection()963         public void onInvalidSelection() {
964             ContactListFilter filter;
965             ContactListFilter currentFilter = mAllFragment.getFilter();
966             if (currentFilter != null
967                     && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
968                 filter = ContactListFilter.createFilterWithType(
969                         ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
970                 mAllFragment.setFilter(filter);
971             } else {
972                 filter = ContactListFilter.createFilterWithType(
973                         ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
974                 mAllFragment.setFilter(filter, false);
975             }
976             mContactListFilterController.setContactListFilter(filter, true);
977         }
978     }
979 
980     private final class CheckBoxListListener implements OnCheckBoxListActionListener {
981         @Override
onStartDisplayingCheckBoxes()982         public void onStartDisplayingCheckBoxes() {
983             mActionBarAdapter.setSelectionMode(true);
984             invalidateOptionsMenu();
985         }
986 
987         @Override
onSelectedContactIdsChanged()988         public void onSelectedContactIdsChanged() {
989             mActionBarAdapter.setSelectionCount(mAllFragment.getSelectedContactIds().size());
990             invalidateOptionsMenu();
991         }
992 
993         @Override
onStopDisplayingCheckBoxes()994         public void onStopDisplayingCheckBoxes() {
995             mActionBarAdapter.setSelectionMode(false);
996         }
997     }
998 
999     private class ContactsUnavailableFragmentListener
1000             implements OnContactsUnavailableActionListener {
ContactsUnavailableFragmentListener()1001         ContactsUnavailableFragmentListener() {}
1002 
1003         @Override
onCreateNewContactAction()1004         public void onCreateNewContactAction() {
1005             ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this,
1006                     EditorIntents.createCompactInsertContactIntent());
1007         }
1008 
1009         @Override
onAddAccountAction()1010         public void onAddAccountAction() {
1011             final Intent intent = ImplicitIntentsUtil.getIntentForAddingAccount();
1012             ImplicitIntentsUtil.startActivityOutsideApp(PeopleActivity.this, intent);
1013         }
1014 
1015         @Override
onImportContactsFromFileAction()1016         public void onImportContactsFromFileAction() {
1017             showImportExportDialogFragment();
1018         }
1019     }
1020 
1021     private final class StrequentContactListFragmentListener
1022             implements ContactTileListFragment.Listener {
StrequentContactListFragmentListener()1023         StrequentContactListFragmentListener() {}
1024 
1025         @Override
onContactSelected(Uri contactUri, Rect targetRect)1026         public void onContactSelected(Uri contactUri, Rect targetRect) {
1027             final Intent intent = ImplicitIntentsUtil.composeQuickContactIntent(contactUri,
1028                     QuickContactActivity.MODE_FULLY_EXPANDED);
1029             intent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.FAVORITES);
1030             ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
1031         }
1032 
1033         @Override
onCallNumberDirectly(String phoneNumber)1034         public void onCallNumberDirectly(String phoneNumber) {
1035             // No need to call phone number directly from People app.
1036             Log.w(TAG, "unexpected invocation of onCallNumberDirectly()");
1037         }
1038     }
1039 
1040     @Override
onCreateOptionsMenu(Menu menu)1041     public boolean onCreateOptionsMenu(Menu menu) {
1042         if (!areContactsAvailable()) {
1043             // If contacts aren't available, hide all menu items.
1044             return false;
1045         }
1046         super.onCreateOptionsMenu(menu);
1047 
1048         MenuInflater inflater = getMenuInflater();
1049         inflater.inflate(R.menu.people_options, menu);
1050 
1051         return true;
1052     }
1053 
invalidateOptionsMenuIfNeeded()1054     private void invalidateOptionsMenuIfNeeded() {
1055         if (isOptionsMenuChanged()) {
1056             invalidateOptionsMenu();
1057         }
1058     }
1059 
isOptionsMenuChanged()1060     public boolean isOptionsMenuChanged() {
1061         if (mOptionsMenuContactsAvailable != areContactsAvailable()) {
1062             return true;
1063         }
1064 
1065         if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) {
1066             return true;
1067         }
1068 
1069         return false;
1070     }
1071 
1072     @Override
onPrepareOptionsMenu(Menu menu)1073     public boolean onPrepareOptionsMenu(Menu menu) {
1074         mOptionsMenuContactsAvailable = areContactsAvailable();
1075         if (!mOptionsMenuContactsAvailable) {
1076             return false;
1077         }
1078 
1079         // Get references to individual menu items in the menu
1080         final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter);
1081         final MenuItem clearFrequentsMenu = menu.findItem(R.id.menu_clear_frequents);
1082         final MenuItem helpMenu = menu.findItem(R.id.menu_help);
1083 
1084         final boolean isSearchOrSelectionMode = mActionBarAdapter.isSearchMode()
1085                 || mActionBarAdapter.isSelectionMode();
1086         if (isSearchOrSelectionMode) {
1087             contactsFilterMenu.setVisible(false);
1088             clearFrequentsMenu.setVisible(false);
1089             helpMenu.setVisible(false);
1090         } else {
1091             switch (getTabPositionForTextDirection(mActionBarAdapter.getCurrentTab())) {
1092                 case TabState.FAVORITES:
1093                     contactsFilterMenu.setVisible(false);
1094                     clearFrequentsMenu.setVisible(hasFrequents());
1095                     break;
1096                 case TabState.ALL:
1097                     contactsFilterMenu.setVisible(true);
1098                     clearFrequentsMenu.setVisible(false);
1099                     break;
1100             }
1101             helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable());
1102         }
1103         final boolean showMiscOptions = !isSearchOrSelectionMode;
1104         final boolean showBlockedNumbers = PhoneCapabilityTester.isPhone(this)
1105                 && ContactsUtils.FLAG_N_FEATURE
1106                 && BlockedNumberContractCompat.canCurrentUserBlockNumbers(this);
1107         makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions);
1108         makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions);
1109         makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions);
1110         makeMenuItemVisible(menu, R.id.menu_blocked_numbers, showMiscOptions && showBlockedNumbers);
1111         makeMenuItemVisible(menu, R.id.menu_settings,
1112                 showMiscOptions && !ContactsPreferenceActivity.isEmpty(this));
1113 
1114         final boolean showSelectedContactOptions = mActionBarAdapter.isSelectionMode()
1115                 && mAllFragment.getSelectedContactIds().size() != 0;
1116         makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions);
1117         makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions);
1118         final boolean showLinkContactsOptions = mActionBarAdapter.isSelectionMode()
1119                 && mAllFragment.getSelectedContactIds().size() > 1;
1120         makeMenuItemVisible(menu, R.id.menu_join, showLinkContactsOptions);
1121 
1122         // Debug options need to be visible even in search mode.
1123         makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions &&
1124                 hasExportIntentHandler());
1125 
1126         return true;
1127     }
1128 
hasExportIntentHandler()1129     private boolean hasExportIntentHandler() {
1130         final Intent intent = new Intent();
1131         intent.setAction("com.android.providers.contacts.DUMP_DATABASE");
1132         final List<ResolveInfo> receivers = getPackageManager().queryIntentActivities(intent,
1133                 PackageManager.MATCH_DEFAULT_ONLY);
1134         return receivers != null && receivers.size() > 0;
1135     }
1136 
1137     /**
1138      * Returns whether there are any frequently contacted people being displayed
1139      * @return
1140      */
hasFrequents()1141     private boolean hasFrequents() {
1142         return mFavoritesFragment.hasFrequents();
1143     }
1144 
makeMenuItemVisible(Menu menu, int itemId, boolean visible)1145     private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
1146         final MenuItem item = menu.findItem(itemId);
1147         if (item != null) {
1148             item.setVisible(visible);
1149         }
1150     }
1151 
makeMenuItemEnabled(Menu menu, int itemId, boolean visible)1152     private void makeMenuItemEnabled(Menu menu, int itemId, boolean visible) {
1153         final MenuItem item = menu.findItem(itemId);
1154         if (item != null) {
1155             item.setEnabled(visible);
1156         }
1157     }
1158 
1159     @Override
onOptionsItemSelected(MenuItem item)1160     public boolean onOptionsItemSelected(MenuItem item) {
1161         if (mDisableOptionItemSelected) {
1162             return false;
1163         }
1164 
1165         switch (item.getItemId()) {
1166             case android.R.id.home: {
1167                 // The home icon on the action bar is pressed
1168                 if (mActionBarAdapter.isUpShowing()) {
1169                     // "UP" icon press -- should be treated as "back".
1170                     onBackPressed();
1171                 }
1172                 return true;
1173             }
1174             case R.id.menu_settings: {
1175                 startActivity(new Intent(this, ContactsPreferenceActivity.class));
1176                 return true;
1177             }
1178             case R.id.menu_contacts_filter: {
1179                 AccountFilterUtil.startAccountFilterActivityForResult(
1180                         this, SUBACTIVITY_ACCOUNT_FILTER,
1181                         mContactListFilterController.getFilter());
1182                 return true;
1183             }
1184             case R.id.menu_search: {
1185                 onSearchRequested();
1186                 return true;
1187             }
1188             case R.id.menu_share:
1189                 shareSelectedContacts();
1190                 return true;
1191             case R.id.menu_join:
1192                 joinSelectedContacts();
1193                 return true;
1194             case R.id.menu_delete:
1195                 deleteSelectedContacts();
1196                 return true;
1197             case R.id.menu_import_export: {
1198                 showImportExportDialogFragment();
1199                 return true;
1200             }
1201             case R.id.menu_clear_frequents: {
1202                 ClearFrequentsDialog.show(getFragmentManager());
1203                 return true;
1204             }
1205             case R.id.menu_help:
1206                 HelpUtils.launchHelpAndFeedbackForMainScreen(this);
1207                 return true;
1208             case R.id.menu_accounts: {
1209                 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
1210                 intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
1211                     ContactsContract.AUTHORITY
1212                 });
1213                 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1214                 ImplicitIntentsUtil.startActivityInAppIfPossible(this, intent);
1215                 return true;
1216             }
1217             case R.id.menu_blocked_numbers: {
1218                 final Intent intent = TelecomManagerUtil.createManageBlockedNumbersIntent(
1219                         (TelecomManager) getSystemService(Context.TELECOM_SERVICE));
1220                 if (intent != null) {
1221                     startActivity(intent);
1222                 }
1223                 return true;
1224             }
1225             case R.id.export_database: {
1226                 final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
1227                 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1228                 ImplicitIntentsUtil.startActivityOutsideApp(this, intent);
1229                 return true;
1230             }
1231         }
1232         return false;
1233     }
1234 
showImportExportDialogFragment()1235     private void showImportExportDialogFragment(){
1236         final boolean isOnFavoriteTab = mTabPagerAdapter.mCurrentPrimaryItem == mFavoritesFragment;
1237         if (isOnFavoriteTab) {
1238             ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
1239                     PeopleActivity.class, ImportExportDialogFragment.EXPORT_MODE_FAVORITES);
1240         } else {
1241             ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
1242                     PeopleActivity.class, ImportExportDialogFragment.EXPORT_MODE_ALL_CONTACTS);
1243         }
1244     }
1245 
1246     @Override
onSearchRequested()1247     public boolean onSearchRequested() { // Search key pressed.
1248         if (!mActionBarAdapter.isSelectionMode()) {
1249             mActionBarAdapter.setSearchMode(true);
1250         }
1251         return true;
1252     }
1253 
1254     /**
1255      * Share all contacts that are currently selected in mAllFragment. This method is pretty
1256      * inefficient for handling large numbers of contacts. I don't expect this to be a problem.
1257      */
shareSelectedContacts()1258     private void shareSelectedContacts() {
1259         final StringBuilder uriListBuilder = new StringBuilder();
1260         for (Long contactId : mAllFragment.getSelectedContactIds()) {
1261             final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
1262             final Uri lookupUri = Contacts.getLookupUri(getContentResolver(), contactUri);
1263             if (lookupUri == null) {
1264                 continue;
1265             }
1266             final List<String> pathSegments = lookupUri.getPathSegments();
1267             if (pathSegments.size() < 2) {
1268                 continue;
1269             }
1270             final String lookupKey = pathSegments.get(pathSegments.size() - 2);
1271             if (uriListBuilder.length() > 0) {
1272                 uriListBuilder.append(':');
1273             }
1274             uriListBuilder.append(Uri.encode(lookupKey));
1275         }
1276         if (uriListBuilder.length() == 0) {
1277             return;
1278         }
1279         final Uri uri = Uri.withAppendedPath(
1280                 Contacts.CONTENT_MULTI_VCARD_URI,
1281                 Uri.encode(uriListBuilder.toString()));
1282         final Intent intent = new Intent(Intent.ACTION_SEND);
1283         intent.setType(Contacts.CONTENT_VCARD_TYPE);
1284         intent.putExtra(Intent.EXTRA_STREAM, uri);
1285         ImplicitIntentsUtil.startActivityOutsideApp(this, intent);
1286     }
1287 
joinSelectedContacts()1288     private void joinSelectedContacts() {
1289         JoinContactsDialogFragment.start(this, mAllFragment.getSelectedContactIds());
1290     }
1291 
1292     @Override
onContactsJoined()1293     public void onContactsJoined() {
1294         mActionBarAdapter.setSelectionMode(false);
1295     }
1296 
deleteSelectedContacts()1297     private void deleteSelectedContacts() {
1298         ContactMultiDeletionInteraction.start(PeopleActivity.this,
1299                 mAllFragment.getSelectedContactIds());
1300     }
1301 
1302     @Override
onDeletionFinished()1303     public void onDeletionFinished() {
1304         mActionBarAdapter.setSelectionMode(false);
1305     }
1306 
1307     @Override
onActivityResult(int requestCode, int resultCode, Intent data)1308     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1309         switch (requestCode) {
1310             case SUBACTIVITY_ACCOUNT_FILTER: {
1311                 AccountFilterUtil.handleAccountFilterResult(
1312                         mContactListFilterController, resultCode, data);
1313                 break;
1314             }
1315 
1316             // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
1317             // anymore
1318             case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
1319                 if (resultCode == RESULT_OK) {
1320                     mAllFragment.onPickerResult(data);
1321                 }
1322 
1323 // TODO fix or remove multipicker code
1324 //                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
1325 //                    // Finish the activity if the sub activity was canceled as back key is used
1326 //                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
1327 //                    finish();
1328 //                }
1329 //                break;
1330         }
1331     }
1332 
1333     @Override
onKeyDown(int keyCode, KeyEvent event)1334     public boolean onKeyDown(int keyCode, KeyEvent event) {
1335         // TODO move to the fragment
1336 
1337         // Bring up the search UI if the user starts typing
1338         final int unicodeChar = event.getUnicodeChar();
1339         if ((unicodeChar != 0)
1340                 // If COMBINING_ACCENT is set, it's not a unicode character.
1341                 && ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0)
1342                 && !Character.isWhitespace(unicodeChar)) {
1343             if (mActionBarAdapter.isSelectionMode()) {
1344                 // Ignore keyboard input when in selection mode.
1345                 return true;
1346             }
1347             String query = new String(new int[]{unicodeChar}, 0, 1);
1348             if (!mActionBarAdapter.isSearchMode()) {
1349                 mActionBarAdapter.setSearchMode(true);
1350                 mActionBarAdapter.setQueryString(query);
1351                 return true;
1352             }
1353         }
1354 
1355         return super.onKeyDown(keyCode, event);
1356     }
1357 
1358     @Override
onBackPressed()1359     public void onBackPressed() {
1360         if (!isSafeToCommitTransactions()) {
1361             return;
1362         }
1363 
1364         if (mActionBarAdapter.isSelectionMode()) {
1365             mActionBarAdapter.setSelectionMode(false);
1366             mAllFragment.displayCheckBoxes(false);
1367         } else if (mActionBarAdapter.isSearchMode()) {
1368             mActionBarAdapter.setSearchMode(false);
1369 
1370             if (mAllFragment.wasSearchResultClicked()) {
1371                 mAllFragment.resetSearchResultClicked();
1372             } else {
1373                 Logger.logScreenView(this, ScreenType.SEARCH_EXIT);
1374                 Logger.logSearchEvent(mAllFragment.createSearchState());
1375             }
1376         } else {
1377             super.onBackPressed();
1378         }
1379     }
1380 
1381     @Override
onSaveInstanceState(Bundle outState)1382     protected void onSaveInstanceState(Bundle outState) {
1383         super.onSaveInstanceState(outState);
1384         mActionBarAdapter.onSaveInstanceState(outState);
1385 
1386         // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
1387         // in order to avoid doing fragment transactions after it.
1388         // TODO Figure out a better way to deal with the issue.
1389         mDisableOptionItemSelected = true;
1390         mActionBarAdapter.setListener(null);
1391         if (mTabPager != null) {
1392             mTabPager.setOnPageChangeListener(null);
1393         }
1394     }
1395 
1396     @Override
onRestoreInstanceState(Bundle savedInstanceState)1397     protected void onRestoreInstanceState(Bundle savedInstanceState) {
1398         super.onRestoreInstanceState(savedInstanceState);
1399         // In our own lifecycle, the focus is saved and restore but later taken away by the
1400         // ViewPager. As a hack, we force focus on the SearchView if we know that we are searching.
1401         // This fixes the keyboard going away on screen rotation
1402         if (mActionBarAdapter.isSearchMode()) {
1403             mActionBarAdapter.setFocusOnSearchView();
1404         }
1405     }
1406 
1407     @Override
getDialogManager()1408     public DialogManager getDialogManager() {
1409         return mDialogManager;
1410     }
1411 
1412     @Override
onClick(View view)1413     public void onClick(View view) {
1414         switch (view.getId()) {
1415             case R.id.floating_action_button:
1416                 Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
1417                 Bundle extras = getIntent().getExtras();
1418                 if (extras != null) {
1419                     intent.putExtras(extras);
1420                 }
1421                 try {
1422                     ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
1423                 } catch (ActivityNotFoundException ex) {
1424                     Toast.makeText(PeopleActivity.this, R.string.missing_app,
1425                             Toast.LENGTH_SHORT).show();
1426                 }
1427                 break;
1428         default:
1429             Log.wtf(TAG, "Unexpected onClick event from " + view);
1430         }
1431     }
1432 
1433     /**
1434      * Returns the tab position adjusted for the text direction.
1435      */
getTabPositionForTextDirection(int position)1436     private int getTabPositionForTextDirection(int position) {
1437         if (isRTL()) {
1438             return TabState.COUNT - 1 - position;
1439         }
1440         return position;
1441     }
1442 }
1443