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