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