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