Home | History | Annotate | Download | only in activities
      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.ContentValues;
     24 import android.content.Intent;
     25 import android.graphics.Rect;
     26 import android.net.Uri;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.os.Parcelable;
     30 import android.os.UserManager;
     31 import android.preference.PreferenceActivity;
     32 import android.provider.ContactsContract;
     33 import android.provider.ContactsContract.Contacts;
     34 import android.provider.ContactsContract.ProviderStatus;
     35 import android.provider.ContactsContract.QuickContact;
     36 import android.provider.Settings;
     37 import android.support.v13.app.FragmentPagerAdapter;
     38 import android.support.v4.view.PagerAdapter;
     39 import android.support.v4.view.ViewPager;
     40 import android.text.TextUtils;
     41 import android.util.Log;
     42 import android.view.KeyCharacterMap;
     43 import android.view.KeyEvent;
     44 import android.view.Menu;
     45 import android.view.MenuInflater;
     46 import android.view.MenuItem;
     47 import android.view.MenuItem.OnMenuItemClickListener;
     48 import android.view.View;
     49 import android.view.ViewGroup;
     50 import android.widget.Toast;
     51 
     52 import com.android.contacts.ContactSaveService;
     53 import com.android.contacts.ContactsActivity;
     54 import com.android.contacts.ContactsUtils;
     55 import com.android.contacts.R;
     56 import com.android.contacts.activities.ActionBarAdapter.TabState;
     57 import com.android.contacts.detail.ContactDetailFragment;
     58 import com.android.contacts.detail.ContactDetailLayoutController;
     59 import com.android.contacts.detail.ContactDetailUpdatesFragment;
     60 import com.android.contacts.detail.ContactLoaderFragment;
     61 import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener;
     62 import com.android.contacts.common.dialog.ClearFrequentsDialog;
     63 import com.android.contacts.group.GroupBrowseListFragment;
     64 import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener;
     65 import com.android.contacts.group.GroupDetailFragment;
     66 import com.android.contacts.interactions.ContactDeletionInteraction;
     67 import com.android.contacts.common.interactions.ImportExportDialogFragment;
     68 import com.android.contacts.list.ContactBrowseListFragment;
     69 import com.android.contacts.common.list.ContactEntryListFragment;
     70 import com.android.contacts.common.list.ContactListFilter;
     71 import com.android.contacts.common.list.ContactListFilterController;
     72 import com.android.contacts.common.list.ContactTileAdapter.DisplayType;
     73 import com.android.contacts.list.ContactTileFrequentFragment;
     74 import com.android.contacts.list.ContactTileListFragment;
     75 import com.android.contacts.list.ContactsIntentResolver;
     76 import com.android.contacts.list.ContactsRequest;
     77 import com.android.contacts.list.ContactsUnavailableFragment;
     78 import com.android.contacts.list.DefaultContactBrowseListFragment;
     79 import com.android.contacts.common.list.DirectoryListLoader;
     80 import com.android.contacts.list.OnContactBrowserActionListener;
     81 import com.android.contacts.list.OnContactsUnavailableActionListener;
     82 import com.android.contacts.list.ProviderStatusWatcher;
     83 import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
     84 import com.android.contacts.model.Contact;
     85 import com.android.contacts.common.model.account.AccountWithDataSet;
     86 import com.android.contacts.preference.ContactsPreferenceActivity;
     87 import com.android.contacts.preference.DisplayOptionsPreferenceFragment;
     88 import com.android.contacts.common.util.AccountFilterUtil;
     89 import com.android.contacts.util.AccountPromptUtils;
     90 import com.android.contacts.common.util.Constants;
     91 import com.android.contacts.util.DialogManager;
     92 import com.android.contacts.util.HelpUtils;
     93 import com.android.contacts.util.PhoneCapabilityTester;
     94 import com.android.contacts.common.util.UriUtils;
     95 import com.android.contacts.widget.TransitionAnimationView;
     96 
     97 import java.util.ArrayList;
     98 import java.util.Locale;
     99 import java.util.concurrent.atomic.AtomicInteger;
    100 
    101 /**
    102  * Displays a list to browse contacts. For xlarge screens, this also displays a detail-pane on
    103  * the right.
    104  */
    105 public class PeopleActivity extends ContactsActivity
    106         implements View.OnCreateContextMenuListener, ActionBarAdapter.Listener,
    107         DialogManager.DialogShowingViewActivity,
    108         ContactListFilterController.ContactListFilterListener, ProviderStatusListener {
    109 
    110     private static final String TAG = "PeopleActivity";
    111 
    112     /** Shows a toogle button for hiding/showing updates. Don't submit with true */
    113     private static final boolean DEBUG_TRANSITIONS = false;
    114 
    115     private static final int TAB_FADE_IN_DURATION = 500;
    116 
    117     private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
    118 
    119     // These values needs to start at 2. See {@link ContactEntryListFragment}.
    120     private static final int SUBACTIVITY_NEW_CONTACT = 2;
    121     private static final int SUBACTIVITY_EDIT_CONTACT = 3;
    122     private static final int SUBACTIVITY_NEW_GROUP = 4;
    123     private static final int SUBACTIVITY_EDIT_GROUP = 5;
    124     private static final int SUBACTIVITY_ACCOUNT_FILTER = 6;
    125 
    126     private final DialogManager mDialogManager = new DialogManager(this);
    127 
    128     private ContactsIntentResolver mIntentResolver;
    129     private ContactsRequest mRequest;
    130 
    131     private ActionBarAdapter mActionBarAdapter;
    132 
    133     private ContactDetailFragment mContactDetailFragment;
    134 
    135     private ContactLoaderFragment mContactDetailLoaderFragment;
    136     private final ContactDetailLoaderFragmentListener mContactDetailLoaderFragmentListener =
    137             new ContactDetailLoaderFragmentListener();
    138 
    139     private GroupDetailFragment mGroupDetailFragment;
    140     private final GroupDetailFragmentListener mGroupDetailFragmentListener =
    141             new GroupDetailFragmentListener();
    142 
    143     private ContactTileListFragment.Listener mFavoritesFragmentListener =
    144             new StrequentContactListFragmentListener();
    145 
    146     private ContactListFilterController mContactListFilterController;
    147 
    148     private ContactsUnavailableFragment mContactsUnavailableFragment;
    149     private ProviderStatusWatcher mProviderStatusWatcher;
    150     private ProviderStatusWatcher.Status mProviderStatus;
    151 
    152     private boolean mOptionsMenuContactsAvailable;
    153 
    154     /**
    155      * Showing a list of Contacts. Also used for showing search results in search mode.
    156      */
    157     private DefaultContactBrowseListFragment mAllFragment;
    158     private ContactTileListFragment mFavoritesFragment;
    159     private ContactTileFrequentFragment mFrequentFragment;
    160     private GroupBrowseListFragment mGroupsFragment;
    161 
    162     private View mFavoritesView;
    163     private View mBrowserView;
    164     private TransitionAnimationView mPeopleActivityView;
    165     private TransitionAnimationView mContactDetailsView;
    166     private TransitionAnimationView mGroupDetailsView;
    167 
    168     /** ViewPager for swipe, used only on the phone (i.e. one-pane mode) */
    169     private ViewPager mTabPager;
    170     private TabPagerAdapter mTabPagerAdapter;
    171     private final TabPagerListener mTabPagerListener = new TabPagerListener();
    172 
    173     private ContactDetailLayoutController mContactDetailLayoutController;
    174 
    175     private boolean mEnableDebugMenuOptions;
    176 
    177     private final Handler mHandler = new Handler();
    178 
    179     /**
    180      * True if this activity instance is a re-created one.  i.e. set true after orientation change.
    181      * This is set in {@link #onCreate} for later use in {@link #onStart}.
    182      */
    183     private boolean mIsRecreatedInstance;
    184 
    185     /**
    186      * If {@link #configureFragments(boolean)} is already called.  Used to avoid calling it twice
    187      * in {@link #onStart}.
    188      * (This initialization only needs to be done once in onStart() when the Activity was just
    189      * created from scratch -- i.e. onCreate() was just called)
    190      */
    191     private boolean mFragmentInitialized;
    192 
    193     /**
    194      * Whether or not the current contact filter is valid or not. We need to do a check on
    195      * start of the app to verify that the user is not in single contact mode. If so, we should
    196      * dynamically change the filter, unless the incoming intent specifically requested a contact
    197      * that should be displayed in that mode.
    198      */
    199     private boolean mCurrentFilterIsValid;
    200 
    201     /** Sequential ID assigned to each instance; used for logging */
    202     private final int mInstanceId;
    203     private static final AtomicInteger sNextInstanceId = new AtomicInteger();
    204 
    205     public PeopleActivity() {
    206         mInstanceId = sNextInstanceId.getAndIncrement();
    207         mIntentResolver = new ContactsIntentResolver(this);
    208         mProviderStatusWatcher = ProviderStatusWatcher.getInstance(this);
    209     }
    210 
    211     @Override
    212     public String toString() {
    213         // Shown on logcat
    214         return String.format("%s@%d", getClass().getSimpleName(), mInstanceId);
    215     }
    216 
    217     public boolean areContactsAvailable() {
    218         return (mProviderStatus != null)
    219                 && mProviderStatus.status == ProviderStatus.STATUS_NORMAL;
    220     }
    221 
    222     private boolean areContactWritableAccountsAvailable() {
    223         return ContactsUtils.areContactWritableAccountsAvailable(this);
    224     }
    225 
    226     private boolean areGroupWritableAccountsAvailable() {
    227         return ContactsUtils.areGroupWritableAccountsAvailable(this);
    228     }
    229 
    230     /**
    231      * Initialize fragments that are (or may not be) in the layout.
    232      *
    233      * For the fragments that are in the layout, we initialize them in
    234      * {@link #createViewsAndFragments(Bundle)} after inflating the layout.
    235      *
    236      * However, there are special fragments which may not be in the layout, so we have to do the
    237      * initialization here.
    238      * The target fragments are:
    239      * - {@link ContactDetailFragment} and {@link ContactDetailUpdatesFragment}:  They may not be
    240      *   in the layout depending on the configuration.  (i.e. portrait)
    241      * - {@link ContactsUnavailableFragment}: We always create it at runtime.
    242      */
    243     @Override
    244     public void onAttachFragment(Fragment fragment) {
    245         if (fragment instanceof ContactDetailFragment) {
    246             mContactDetailFragment = (ContactDetailFragment) fragment;
    247         } else if (fragment instanceof ContactsUnavailableFragment) {
    248             mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment;
    249             mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
    250                     new ContactsUnavailableFragmentListener());
    251         }
    252     }
    253 
    254     @Override
    255     protected void onCreate(Bundle savedState) {
    256         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
    257             Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start");
    258         }
    259         super.onCreate(savedState);
    260 
    261         if (!processIntent(false)) {
    262             finish();
    263             return;
    264         }
    265         mContactListFilterController = ContactListFilterController.getInstance(this);
    266         mContactListFilterController.checkFilterValidity(false);
    267         mContactListFilterController.addListener(this);
    268 
    269         mProviderStatusWatcher.addListener(this);
    270 
    271         mIsRecreatedInstance = (savedState != null);
    272         createViewsAndFragments(savedState);
    273         getWindow().setBackgroundDrawableResource(R.color.background_primary);
    274         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
    275             Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish");
    276         }
    277     }
    278 
    279     @Override
    280     protected void onNewIntent(Intent intent) {
    281         setIntent(intent);
    282         if (!processIntent(true)) {
    283             finish();
    284             return;
    285         }
    286         mActionBarAdapter.initialize(null, mRequest);
    287 
    288         mContactListFilterController.checkFilterValidity(false);
    289         mCurrentFilterIsValid = true;
    290 
    291         // Re-configure fragments.
    292         configureFragments(true /* from request */);
    293         invalidateOptionsMenuIfNeeded();
    294     }
    295 
    296     /**
    297      * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect
    298      * is needed.
    299      *
    300      * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}.
    301      * @return {@code true} if {@link PeopleActivity} should continue running.  {@code false}
    302      *         if it shouldn't, in which case the caller should finish() itself and shouldn't do
    303      *         farther initialization.
    304      */
    305     private boolean processIntent(boolean forNewIntent) {
    306         // Extract relevant information from the intent
    307         mRequest = mIntentResolver.resolveIntent(getIntent());
    308         if (Log.isLoggable(TAG, Log.DEBUG)) {
    309             Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent
    310                     + " intent=" + getIntent() + " request=" + mRequest);
    311         }
    312         if (!mRequest.isValid()) {
    313             setResult(RESULT_CANCELED);
    314             return false;
    315         }
    316 
    317         Intent redirect = mRequest.getRedirectIntent();
    318         if (redirect != null) {
    319             // Need to start a different activity
    320             startActivity(redirect);
    321             return false;
    322         }
    323 
    324         if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT
    325                 && !PhoneCapabilityTester.isUsingTwoPanes(this)) {
    326             redirect = new Intent(this, ContactDetailActivity.class);
    327             redirect.setAction(Intent.ACTION_VIEW);
    328             redirect.setData(mRequest.getContactUri());
    329             startActivity(redirect);
    330             return false;
    331         }
    332         return true;
    333     }
    334 
    335     private void createViewsAndFragments(Bundle savedState) {
    336         setContentView(R.layout.people_activity);
    337 
    338         final FragmentManager fragmentManager = getFragmentManager();
    339 
    340         // Hide all tabs (the current tab will later be reshown once a tab is selected)
    341         final FragmentTransaction transaction = fragmentManager.beginTransaction();
    342 
    343         // Prepare the fragments which are used both on 1-pane and on 2-pane.
    344         final boolean isUsingTwoPanes = PhoneCapabilityTester.isUsingTwoPanes(this);
    345         if (isUsingTwoPanes) {
    346             mFavoritesFragment = getFragment(R.id.favorites_fragment);
    347             mAllFragment = getFragment(R.id.all_fragment);
    348             mGroupsFragment = getFragment(R.id.groups_fragment);
    349         } else {
    350             mTabPager = getView(R.id.tab_pager);
    351             mTabPagerAdapter = new TabPagerAdapter();
    352             mTabPager.setAdapter(mTabPagerAdapter);
    353             mTabPager.setOnPageChangeListener(mTabPagerListener);
    354 
    355             final String FAVORITE_TAG = "tab-pager-favorite";
    356             final String ALL_TAG = "tab-pager-all";
    357             final String GROUPS_TAG = "tab-pager-groups";
    358 
    359             // Create the fragments and add as children of the view pager.
    360             // The pager adapter will only change the visibility; it'll never create/destroy
    361             // fragments.
    362             // However, if it's after screen rotation, the fragments have been re-created by
    363             // the fragment manager, so first see if there're already the target fragments
    364             // existing.
    365             mFavoritesFragment = (ContactTileListFragment)
    366                     fragmentManager.findFragmentByTag(FAVORITE_TAG);
    367             mAllFragment = (DefaultContactBrowseListFragment)
    368                     fragmentManager.findFragmentByTag(ALL_TAG);
    369             mGroupsFragment = (GroupBrowseListFragment)
    370                     fragmentManager.findFragmentByTag(GROUPS_TAG);
    371 
    372             if (mFavoritesFragment == null) {
    373                 mFavoritesFragment = new ContactTileListFragment();
    374                 mAllFragment = new DefaultContactBrowseListFragment();
    375                 mGroupsFragment = new GroupBrowseListFragment();
    376 
    377                 transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG);
    378                 transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG);
    379                 transaction.add(R.id.tab_pager, mGroupsFragment, GROUPS_TAG);
    380             }
    381         }
    382 
    383         mFavoritesFragment.setListener(mFavoritesFragmentListener);
    384 
    385         mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener());
    386 
    387         mGroupsFragment.setListener(new GroupBrowserActionListener());
    388 
    389         // Hide all fragments for now.  We adjust visibility when we get onSelectedTabChanged()
    390         // from ActionBarAdapter.
    391         transaction.hide(mFavoritesFragment);
    392         transaction.hide(mAllFragment);
    393         transaction.hide(mGroupsFragment);
    394 
    395         if (isUsingTwoPanes) {
    396             // Prepare 2-pane only fragments/views...
    397 
    398             // Container views for fragments
    399             mPeopleActivityView = getView(R.id.people_view);
    400             mFavoritesView = getView(R.id.favorites_view);
    401             mContactDetailsView = getView(R.id.contact_details_view);
    402             mGroupDetailsView = getView(R.id.group_details_view);
    403             mBrowserView = getView(R.id.browse_view);
    404 
    405             // Only favorites tab with two panes has a separate frequent fragment
    406             if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) {
    407                 mFrequentFragment = getFragment(R.id.frequent_fragment);
    408                 mFrequentFragment.setListener(mFavoritesFragmentListener);
    409                 mFrequentFragment.setDisplayType(DisplayType.FREQUENT_ONLY);
    410                 mFrequentFragment.enableQuickContact(true);
    411             }
    412 
    413             mContactDetailLoaderFragment = getFragment(R.id.contact_detail_loader_fragment);
    414             mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener);
    415 
    416             mGroupDetailFragment = getFragment(R.id.group_detail_fragment);
    417             mGroupDetailFragment.setListener(mGroupDetailFragmentListener);
    418             mGroupDetailFragment.setQuickContact(true);
    419 
    420             if (mContactDetailFragment != null) {
    421                 transaction.hide(mContactDetailFragment);
    422             }
    423             transaction.hide(mGroupDetailFragment);
    424 
    425             // Configure contact details
    426             mContactDetailLayoutController = new ContactDetailLayoutController(this, savedState,
    427                     getFragmentManager(), mContactDetailsView,
    428                     findViewById(R.id.contact_detail_container),
    429                     new ContactDetailFragmentListener());
    430         }
    431         transaction.commitAllowingStateLoss();
    432         fragmentManager.executePendingTransactions();
    433 
    434         // Setting Properties after fragment is created
    435         if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) {
    436             mFavoritesFragment.enableQuickContact(true);
    437             mFavoritesFragment.setDisplayType(DisplayType.STARRED_ONLY);
    438         } else {
    439             // For 2-pane in All and Groups but not in Favorites fragment, show the chevron
    440             // for quick contact popup
    441             mFavoritesFragment.enableQuickContact(isUsingTwoPanes);
    442             mFavoritesFragment.setDisplayType(DisplayType.STREQUENT);
    443         }
    444 
    445         // Configure action bar
    446         mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar(), isUsingTwoPanes);
    447         mActionBarAdapter.initialize(savedState, mRequest);
    448 
    449         invalidateOptionsMenuIfNeeded();
    450     }
    451 
    452     @Override
    453     protected void onStart() {
    454         if (!mFragmentInitialized) {
    455             mFragmentInitialized = true;
    456             /* Configure fragments if we haven't.
    457              *
    458              * Note it's a one-shot initialization, so we want to do this in {@link #onCreate}.
    459              *
    460              * However, because this method may indirectly touch views in fragments but fragments
    461              * created in {@link #configureContentView} using a {@link FragmentTransaction} will NOT
    462              * have views until {@link Activity#onCreate} finishes (they would if they were inflated
    463              * from a layout), we need to do it here in {@link #onStart()}.
    464              *
    465              * (When {@link Fragment#onCreateView} is called is different in the former case and
    466              * in the latter case, unfortunately.)
    467              *
    468              * Also, we skip most of the work in it if the activity is a re-created one.
    469              * (so the argument.)
    470              */
    471             configureFragments(!mIsRecreatedInstance);
    472         } else if (PhoneCapabilityTester.isUsingTwoPanes(this) && !mCurrentFilterIsValid) {
    473             // We only want to do the filter check in onStart for wide screen devices where it
    474             // is often possible to get into single contact mode. Only do this check if
    475             // the filter hasn't already been set properly (i.e. onCreate or onActivityResult).
    476 
    477             // Since there is only one {@link ContactListFilterController} across multiple
    478             // activity instances, make sure the filter controller is in sync withthe current
    479             // contact list fragment filter.
    480             // TODO: Clean this up. Perhaps change {@link ContactListFilterController} to not be a
    481             // singleton?
    482             mContactListFilterController.setContactListFilter(mAllFragment.getFilter(), true);
    483             mContactListFilterController.checkFilterValidity(true);
    484             mCurrentFilterIsValid = true;
    485         }
    486         super.onStart();
    487     }
    488 
    489     @Override
    490     protected void onPause() {
    491         mOptionsMenuContactsAvailable = false;
    492         mProviderStatusWatcher.stop();
    493         super.onPause();
    494     }
    495 
    496     @Override
    497     protected void onResume() {
    498         super.onResume();
    499 
    500         mProviderStatusWatcher.start();
    501         updateViewConfiguration(true);
    502 
    503         // Re-register the listener, which may have been cleared when onSaveInstanceState was
    504         // called.  See also: onSaveInstanceState
    505         mActionBarAdapter.setListener(this);
    506         if (mTabPager != null) {
    507             mTabPager.setOnPageChangeListener(mTabPagerListener);
    508         }
    509         // Current tab may have changed since the last onSaveInstanceState().  Make sure
    510         // the actual contents match the tab.
    511         updateFragmentsVisibility();
    512     }
    513 
    514     @Override
    515     protected void onStop() {
    516         super.onStop();
    517         mCurrentFilterIsValid = false;
    518     }
    519 
    520     @Override
    521     protected void onDestroy() {
    522         mProviderStatusWatcher.removeListener(this);
    523 
    524         // Some of variables will be null if this Activity redirects Intent.
    525         // See also onCreate() or other methods called during the Activity's initialization.
    526         if (mActionBarAdapter != null) {
    527             mActionBarAdapter.setListener(null);
    528         }
    529         if (mContactListFilterController != null) {
    530             mContactListFilterController.removeListener(this);
    531         }
    532 
    533         super.onDestroy();
    534     }
    535 
    536     private void configureFragments(boolean fromRequest) {
    537         if (fromRequest) {
    538             ContactListFilter filter = null;
    539             int actionCode = mRequest.getActionCode();
    540             boolean searchMode = mRequest.isSearchMode();
    541             final int tabToOpen;
    542             switch (actionCode) {
    543                 case ContactsRequest.ACTION_ALL_CONTACTS:
    544                     filter = ContactListFilter.createFilterWithType(
    545                             ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
    546                     tabToOpen = TabState.ALL;
    547                     break;
    548                 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
    549                     filter = ContactListFilter.createFilterWithType(
    550                             ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
    551                     tabToOpen = TabState.ALL;
    552                     break;
    553 
    554                 case ContactsRequest.ACTION_FREQUENT:
    555                 case ContactsRequest.ACTION_STREQUENT:
    556                 case ContactsRequest.ACTION_STARRED:
    557                     tabToOpen = TabState.FAVORITES;
    558                     break;
    559                 case ContactsRequest.ACTION_VIEW_CONTACT:
    560                     // We redirect this intent to the detail activity on 1-pane, so we don't get
    561                     // here.  It's only for 2-pane.
    562                     Uri currentlyLoadedContactUri = mContactDetailFragment.getUri();
    563                     if (currentlyLoadedContactUri != null
    564                             && !mRequest.getContactUri().equals(currentlyLoadedContactUri)) {
    565                         mContactDetailsView.setMaskVisibility(true);
    566                     }
    567                     tabToOpen = TabState.ALL;
    568                     break;
    569                 case ContactsRequest.ACTION_GROUP:
    570                     tabToOpen = TabState.GROUPS;
    571                     break;
    572                 default:
    573                     tabToOpen = -1;
    574                     break;
    575             }
    576             if (tabToOpen != -1) {
    577                 mActionBarAdapter.setCurrentTab(tabToOpen);
    578             }
    579 
    580             if (filter != null) {
    581                 mContactListFilterController.setContactListFilter(filter, false);
    582                 searchMode = false;
    583             }
    584 
    585             if (mRequest.getContactUri() != null) {
    586                 searchMode = false;
    587             }
    588 
    589             mActionBarAdapter.setSearchMode(searchMode);
    590             configureContactListFragmentForRequest();
    591         }
    592 
    593         configureContactListFragment();
    594         configureGroupListFragment();
    595 
    596         invalidateOptionsMenuIfNeeded();
    597     }
    598 
    599     @Override
    600     public void onContactListFilterChanged() {
    601         if (mAllFragment == null || !mAllFragment.isAdded()) {
    602             return;
    603         }
    604 
    605         mAllFragment.setFilter(mContactListFilterController.getFilter());
    606 
    607         invalidateOptionsMenuIfNeeded();
    608     }
    609 
    610     private void setupContactDetailFragment(final Uri contactLookupUri) {
    611         mContactDetailLoaderFragment.loadUri(contactLookupUri);
    612         invalidateOptionsMenuIfNeeded();
    613     }
    614 
    615     private void setupGroupDetailFragment(Uri groupUri) {
    616         // If we are switching from one group to another, do a cross-fade
    617         if (mGroupDetailFragment != null && mGroupDetailFragment.getGroupUri() != null &&
    618                 !UriUtils.areEqual(mGroupDetailFragment.getGroupUri(), groupUri)) {
    619             mGroupDetailsView.startMaskTransition(false, -1);
    620         }
    621         mGroupDetailFragment.loadGroup(groupUri);
    622         invalidateOptionsMenuIfNeeded();
    623     }
    624 
    625     /**
    626      * Handler for action bar actions.
    627      */
    628     @Override
    629     public void onAction(int action) {
    630         switch (action) {
    631             case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
    632                 // Tell the fragments that we're in the search mode
    633                 configureFragments(false /* from request */);
    634                 updateFragmentsVisibility();
    635                 invalidateOptionsMenu();
    636                 break;
    637             case ActionBarAdapter.Listener.Action.STOP_SEARCH_MODE:
    638                 setQueryTextToFragment("");
    639                 updateFragmentsVisibility();
    640                 invalidateOptionsMenu();
    641                 break;
    642             case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
    643                 final String queryString = mActionBarAdapter.getQueryString();
    644                 setQueryTextToFragment(queryString);
    645                 updateDebugOptionsVisibility(
    646                         ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
    647                 break;
    648             default:
    649                 throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action);
    650         }
    651     }
    652 
    653     @Override
    654     public void onSelectedTabChanged() {
    655         updateFragmentsVisibility();
    656     }
    657 
    658     private void updateDebugOptionsVisibility(boolean visible) {
    659         if (mEnableDebugMenuOptions != visible) {
    660             mEnableDebugMenuOptions = visible;
    661             invalidateOptionsMenu();
    662         }
    663     }
    664 
    665     /**
    666      * Updates the fragment/view visibility according to the current mode, such as
    667      * {@link ActionBarAdapter#isSearchMode()} and {@link ActionBarAdapter#getCurrentTab()}.
    668      */
    669     private void updateFragmentsVisibility() {
    670         int tab = mActionBarAdapter.getCurrentTab();
    671 
    672         // We use ViewPager on 1-pane.
    673         if (!PhoneCapabilityTester.isUsingTwoPanes(this)) {
    674             if (mActionBarAdapter.isSearchMode()) {
    675                 mTabPagerAdapter.setSearchMode(true);
    676             } else {
    677                 // No smooth scrolling if quitting from the search mode.
    678                 final boolean wasSearchMode = mTabPagerAdapter.isSearchMode();
    679                 mTabPagerAdapter.setSearchMode(false);
    680                 if (mTabPager.getCurrentItem() != tab) {
    681                     mTabPager.setCurrentItem(tab, !wasSearchMode);
    682                 }
    683             }
    684             invalidateOptionsMenu();
    685             showEmptyStateForTab(tab);
    686             if (tab == TabState.GROUPS) {
    687                 mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
    688             }
    689             return;
    690         }
    691 
    692         // for the tablet...
    693 
    694         // If in search mode, we use the all list + contact details to show the result.
    695         if (mActionBarAdapter.isSearchMode()) {
    696             tab = TabState.ALL;
    697         }
    698 
    699         switch (tab) {
    700             case TabState.FAVORITES:
    701                 mFavoritesView.setVisibility(View.VISIBLE);
    702                 mBrowserView.setVisibility(View.GONE);
    703                 mGroupDetailsView.setVisibility(View.GONE);
    704                 mContactDetailsView.setVisibility(View.GONE);
    705                 break;
    706             case TabState.GROUPS:
    707                 mFavoritesView.setVisibility(View.GONE);
    708                 mBrowserView.setVisibility(View.VISIBLE);
    709                 mGroupDetailsView.setVisibility(View.VISIBLE);
    710                 mContactDetailsView.setVisibility(View.GONE);
    711                 mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
    712                 break;
    713             case TabState.ALL:
    714                 mFavoritesView.setVisibility(View.GONE);
    715                 mBrowserView.setVisibility(View.VISIBLE);
    716                 mContactDetailsView.setVisibility(View.VISIBLE);
    717                 mGroupDetailsView.setVisibility(View.GONE);
    718                 break;
    719         }
    720         mPeopleActivityView.startMaskTransition(false, TAB_FADE_IN_DURATION);
    721         FragmentManager fragmentManager = getFragmentManager();
    722         FragmentTransaction ft = fragmentManager.beginTransaction();
    723 
    724         // Note mContactDetailLoaderFragment is an invisible fragment, but we still have to show/
    725         // hide it so its options menu will be shown/hidden.
    726         switch (tab) {
    727             case TabState.FAVORITES:
    728                 showFragment(ft, mFavoritesFragment);
    729                 showFragment(ft, mFrequentFragment);
    730                 hideFragment(ft, mAllFragment);
    731                 hideFragment(ft, mContactDetailLoaderFragment);
    732                 hideFragment(ft, mContactDetailFragment);
    733                 hideFragment(ft, mGroupsFragment);
    734                 hideFragment(ft, mGroupDetailFragment);
    735                 break;
    736             case TabState.ALL:
    737                 hideFragment(ft, mFavoritesFragment);
    738                 hideFragment(ft, mFrequentFragment);
    739                 showFragment(ft, mAllFragment);
    740                 showFragment(ft, mContactDetailLoaderFragment);
    741                 showFragment(ft, mContactDetailFragment);
    742                 hideFragment(ft, mGroupsFragment);
    743                 hideFragment(ft, mGroupDetailFragment);
    744                 break;
    745             case TabState.GROUPS:
    746                 hideFragment(ft, mFavoritesFragment);
    747                 hideFragment(ft, mFrequentFragment);
    748                 hideFragment(ft, mAllFragment);
    749                 hideFragment(ft, mContactDetailLoaderFragment);
    750                 hideFragment(ft, mContactDetailFragment);
    751                 showFragment(ft, mGroupsFragment);
    752                 showFragment(ft, mGroupDetailFragment);
    753                 break;
    754         }
    755         if (!ft.isEmpty()) {
    756             ft.commitAllowingStateLoss();
    757             fragmentManager.executePendingTransactions();
    758             // When switching tabs, we need to invalidate options menu, but executing a
    759             // fragment transaction does it implicitly.  We don't have to call invalidateOptionsMenu
    760             // manually.
    761         }
    762         showEmptyStateForTab(tab);
    763     }
    764 
    765     private void showEmptyStateForTab(int tab) {
    766         if (mContactsUnavailableFragment != null) {
    767             switch (tab) {
    768                 case TabState.FAVORITES:
    769                     mContactsUnavailableFragment.setMessageText(
    770                             R.string.listTotalAllContactsZeroStarred, -1);
    771                     break;
    772                 case TabState.GROUPS:
    773                     mContactsUnavailableFragment.setMessageText(R.string.noGroups,
    774                             areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts);
    775                     break;
    776                 case TabState.ALL:
    777                     mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1);
    778                     break;
    779             }
    780         }
    781     }
    782 
    783     private class TabPagerListener implements ViewPager.OnPageChangeListener {
    784 
    785         // This package-protected constructor is here because of a possible compiler bug.
    786         // PeopleActivity$1.class should be generated due to the private outer/inner class access
    787         // needed here.  But for some reason, PeopleActivity$1.class is missing.
    788         // Since $1 class is needed as a jvm work around to get access to the inner class,
    789         // changing the constructor to package-protected or public will solve the problem.
    790         // To verify whether $1 class is needed, javap PeopleActivity$TabPagerListener and look for
    791         // references to PeopleActivity$1.
    792         //
    793         // When the constructor is private and PeopleActivity$1.class is missing, proguard will
    794         // correctly catch this and throw warnings and error out the build on user/userdebug builds.
    795         //
    796         // All private inner classes below also need this fix.
    797         TabPagerListener() {}
    798 
    799         @Override
    800         public void onPageScrollStateChanged(int state) {
    801         }
    802 
    803         @Override
    804         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    805         }
    806 
    807         @Override
    808         public void onPageSelected(int position) {
    809             // Make sure not in the search mode, in which case position != TabState.ordinal().
    810             if (!mTabPagerAdapter.isSearchMode()) {
    811                 mActionBarAdapter.setCurrentTab(position, false);
    812                 showEmptyStateForTab(position);
    813                 if (position == TabState.GROUPS) {
    814                     mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
    815                 }
    816                 invalidateOptionsMenu();
    817             }
    818         }
    819     }
    820 
    821     /**
    822      * Adapter for the {@link ViewPager}.  Unlike {@link FragmentPagerAdapter},
    823      * {@link #instantiateItem} returns existing fragments, and {@link #instantiateItem}/
    824      * {@link #destroyItem} show/hide fragments instead of attaching/detaching.
    825      *
    826      * In search mode, we always show the "all" fragment, and disable the swipe.  We change the
    827      * number of items to 1 to disable the swipe.
    828      *
    829      * TODO figure out a more straight way to disable swipe.
    830      */
    831     private class TabPagerAdapter extends PagerAdapter {
    832         private final FragmentManager mFragmentManager;
    833         private FragmentTransaction mCurTransaction = null;
    834 
    835         private boolean mTabPagerAdapterSearchMode;
    836 
    837         private Fragment mCurrentPrimaryItem;
    838 
    839         public TabPagerAdapter() {
    840             mFragmentManager = getFragmentManager();
    841         }
    842 
    843         public boolean isSearchMode() {
    844             return mTabPagerAdapterSearchMode;
    845         }
    846 
    847         public void setSearchMode(boolean searchMode) {
    848             if (searchMode == mTabPagerAdapterSearchMode) {
    849                 return;
    850             }
    851             mTabPagerAdapterSearchMode = searchMode;
    852             notifyDataSetChanged();
    853         }
    854 
    855         @Override
    856         public int getCount() {
    857             return mTabPagerAdapterSearchMode ? 1 : TabState.COUNT;
    858         }
    859 
    860         /** Gets called when the number of items changes. */
    861         @Override
    862         public int getItemPosition(Object object) {
    863             if (mTabPagerAdapterSearchMode) {
    864                 if (object == mAllFragment) {
    865                     return 0; // Only 1 page in search mode
    866                 }
    867             } else {
    868                 if (object == mFavoritesFragment) {
    869                     return TabState.FAVORITES;
    870                 }
    871                 if (object == mAllFragment) {
    872                     return TabState.ALL;
    873                 }
    874                 if (object == mGroupsFragment) {
    875                     return TabState.GROUPS;
    876                 }
    877             }
    878             return POSITION_NONE;
    879         }
    880 
    881         @Override
    882         public void startUpdate(ViewGroup container) {
    883         }
    884 
    885         private Fragment getFragment(int position) {
    886             if (mTabPagerAdapterSearchMode) {
    887                 if (position != 0) {
    888                     // This has only been observed in monkey tests.
    889                     // Let's log this issue, but not crash
    890                     Log.w(TAG, "Request fragment at position=" + position + ", eventhough we " +
    891                             "are in search mode");
    892                 }
    893                 return mAllFragment;
    894             } else {
    895                 if (position == TabState.FAVORITES) {
    896                     return mFavoritesFragment;
    897                 } else if (position == TabState.ALL) {
    898                     return mAllFragment;
    899                 } else if (position == TabState.GROUPS) {
    900                     return mGroupsFragment;
    901                 }
    902             }
    903             throw new IllegalArgumentException("position: " + position);
    904         }
    905 
    906         @Override
    907         public Object instantiateItem(ViewGroup container, int position) {
    908             if (mCurTransaction == null) {
    909                 mCurTransaction = mFragmentManager.beginTransaction();
    910             }
    911             Fragment f = getFragment(position);
    912             mCurTransaction.show(f);
    913 
    914             // Non primary pages are not visible.
    915             f.setUserVisibleHint(f == mCurrentPrimaryItem);
    916             return f;
    917         }
    918 
    919         @Override
    920         public void destroyItem(ViewGroup container, int position, Object object) {
    921             if (mCurTransaction == null) {
    922                 mCurTransaction = mFragmentManager.beginTransaction();
    923             }
    924             mCurTransaction.hide((Fragment) object);
    925         }
    926 
    927         @Override
    928         public void finishUpdate(ViewGroup container) {
    929             if (mCurTransaction != null) {
    930                 mCurTransaction.commitAllowingStateLoss();
    931                 mCurTransaction = null;
    932                 mFragmentManager.executePendingTransactions();
    933             }
    934         }
    935 
    936         @Override
    937         public boolean isViewFromObject(View view, Object object) {
    938             return ((Fragment) object).getView() == view;
    939         }
    940 
    941         @Override
    942         public void setPrimaryItem(ViewGroup container, int position, Object object) {
    943             Fragment fragment = (Fragment) object;
    944             if (mCurrentPrimaryItem != fragment) {
    945                 if (mCurrentPrimaryItem != null) {
    946                     mCurrentPrimaryItem.setUserVisibleHint(false);
    947                 }
    948                 if (fragment != null) {
    949                     fragment.setUserVisibleHint(true);
    950                 }
    951                 mCurrentPrimaryItem = fragment;
    952             }
    953         }
    954 
    955         @Override
    956         public Parcelable saveState() {
    957             return null;
    958         }
    959 
    960         @Override
    961         public void restoreState(Parcelable state, ClassLoader loader) {
    962         }
    963     }
    964 
    965     private void setQueryTextToFragment(String query) {
    966         mAllFragment.setQueryString(query, true);
    967         mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode());
    968     }
    969 
    970     private void configureContactListFragmentForRequest() {
    971         Uri contactUri = mRequest.getContactUri();
    972         if (contactUri != null) {
    973             // For an incoming request, explicitly require a selection if we are on 2-pane UI,
    974             // (i.e. even if we view the same selected contact, the contact may no longer be
    975             // in the list, so we must refresh the list).
    976             if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
    977                 mAllFragment.setSelectionRequired(true);
    978             }
    979             mAllFragment.setSelectedContactUri(contactUri);
    980         }
    981 
    982         mAllFragment.setFilter(mContactListFilterController.getFilter());
    983         setQueryTextToFragment(mActionBarAdapter.getQueryString());
    984 
    985         if (mRequest.isDirectorySearchEnabled()) {
    986             mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
    987         } else {
    988             mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
    989         }
    990     }
    991 
    992     private void configureContactListFragment() {
    993         // Filter may be changed when this Activity is in background.
    994         mAllFragment.setFilter(mContactListFilterController.getFilter());
    995 
    996         final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this);
    997         final Locale locale = Locale.getDefault();
    998         final int layoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
    999         final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL);
   1000         final int position;
   1001         if (useTwoPane)  {
   1002             position = isLayoutRtl ? View.SCROLLBAR_POSITION_RIGHT : View.SCROLLBAR_POSITION_LEFT;
   1003         } else {
   1004             position = isLayoutRtl ? View.SCROLLBAR_POSITION_LEFT: View.SCROLLBAR_POSITION_RIGHT;
   1005         }
   1006         mAllFragment.setVerticalScrollbarPosition(position);
   1007         mAllFragment.setSelectionVisible(useTwoPane);
   1008         mAllFragment.setQuickContactEnabled(!useTwoPane);
   1009     }
   1010 
   1011     private void configureGroupListFragment() {
   1012         final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this);
   1013         mGroupsFragment.setVerticalScrollbarPosition(
   1014                 useTwoPane
   1015                         ? View.SCROLLBAR_POSITION_LEFT
   1016                         : View.SCROLLBAR_POSITION_RIGHT);
   1017         mGroupsFragment.setSelectionVisible(useTwoPane);
   1018     }
   1019 
   1020     @Override
   1021     public void onProviderStatusChange() {
   1022         updateViewConfiguration(false);
   1023     }
   1024 
   1025     private void updateViewConfiguration(boolean forceUpdate) {
   1026         ProviderStatusWatcher.Status providerStatus = mProviderStatusWatcher.getProviderStatus();
   1027         if (!forceUpdate && (mProviderStatus != null)
   1028                 && (providerStatus.status == mProviderStatus.status)) return;
   1029         mProviderStatus = providerStatus;
   1030 
   1031         View contactsUnavailableView = findViewById(R.id.contacts_unavailable_view);
   1032         View mainView = findViewById(R.id.main_view);
   1033 
   1034         if (mProviderStatus.status == ProviderStatus.STATUS_NORMAL) {
   1035             // Ensure that the mTabPager is visible; we may have made it invisible below.
   1036             contactsUnavailableView.setVisibility(View.GONE);
   1037             if (mTabPager != null) {
   1038                 mTabPager.setVisibility(View.VISIBLE);
   1039             }
   1040 
   1041             if (mainView != null) {
   1042                 mainView.setVisibility(View.VISIBLE);
   1043             }
   1044             if (mAllFragment != null) {
   1045                 mAllFragment.setEnabled(true);
   1046             }
   1047         } else {
   1048             // If there are no accounts on the device and we should show the "no account" prompt
   1049             // (based on {@link SharedPreferences}), then launch the account setup activity so the
   1050             // user can sign-in or create an account.
   1051             //
   1052             // Also check for ability to modify accounts.  In limited user mode, you can't modify
   1053             // accounts so there is no point sending users to account setup activity.
   1054             final UserManager userManager = UserManager.get(this);
   1055             final boolean disallowModifyAccounts = userManager.getUserRestrictions().getBoolean(
   1056                     UserManager.DISALLOW_MODIFY_ACCOUNTS);
   1057             if (!disallowModifyAccounts && !areContactWritableAccountsAvailable() &&
   1058                     AccountPromptUtils.shouldShowAccountPrompt(this)) {
   1059                 AccountPromptUtils.launchAccountPrompt(this);
   1060                 return;
   1061             }
   1062 
   1063             // Otherwise, continue setting up the page so that the user can still use the app
   1064             // without an account.
   1065             if (mAllFragment != null) {
   1066                 mAllFragment.setEnabled(false);
   1067             }
   1068             if (mContactsUnavailableFragment == null) {
   1069                 mContactsUnavailableFragment = new ContactsUnavailableFragment();
   1070                 mContactsUnavailableFragment.setOnContactsUnavailableActionListener(
   1071                         new ContactsUnavailableFragmentListener());
   1072                 getFragmentManager().beginTransaction()
   1073                         .replace(R.id.contacts_unavailable_container, mContactsUnavailableFragment)
   1074                         .commitAllowingStateLoss();
   1075             }
   1076             mContactsUnavailableFragment.updateStatus(mProviderStatus);
   1077 
   1078             // Show the contactsUnavailableView, and hide the mTabPager so that we don't
   1079             // see it sliding in underneath the contactsUnavailableView at the edges.
   1080             contactsUnavailableView.setVisibility(View.VISIBLE);
   1081             if (mTabPager != null) {
   1082                 mTabPager.setVisibility(View.GONE);
   1083             }
   1084 
   1085             if (mainView != null) {
   1086                 mainView.setVisibility(View.INVISIBLE);
   1087             }
   1088 
   1089             showEmptyStateForTab(mActionBarAdapter.getCurrentTab());
   1090         }
   1091 
   1092         invalidateOptionsMenuIfNeeded();
   1093     }
   1094 
   1095     private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
   1096         ContactBrowserActionListener() {}
   1097 
   1098         @Override
   1099         public void onSelectionChange() {
   1100             if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
   1101                 setupContactDetailFragment(mAllFragment.getSelectedContactUri());
   1102             }
   1103         }
   1104 
   1105         @Override
   1106         public void onViewContactAction(Uri contactLookupUri) {
   1107             if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
   1108                 setupContactDetailFragment(contactLookupUri);
   1109             } else {
   1110                 Intent intent = new Intent(Intent.ACTION_VIEW, contactLookupUri);
   1111                 startActivity(intent);
   1112             }
   1113         }
   1114 
   1115         @Override
   1116         public void onCreateNewContactAction() {
   1117             Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
   1118             Bundle extras = getIntent().getExtras();
   1119             if (extras != null) {
   1120                 intent.putExtras(extras);
   1121             }
   1122             startActivity(intent);
   1123         }
   1124 
   1125         @Override
   1126         public void onEditContactAction(Uri contactLookupUri) {
   1127             Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
   1128             Bundle extras = getIntent().getExtras();
   1129             if (extras != null) {
   1130                 intent.putExtras(extras);
   1131             }
   1132             intent.putExtra(
   1133                     ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
   1134             startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT);
   1135         }
   1136 
   1137         @Override
   1138         public void onAddToFavoritesAction(Uri contactUri) {
   1139             ContentValues values = new ContentValues(1);
   1140             values.put(Contacts.STARRED, 1);
   1141             getContentResolver().update(contactUri, values, null, null);
   1142         }
   1143 
   1144         @Override
   1145         public void onRemoveFromFavoritesAction(Uri contactUri) {
   1146             ContentValues values = new ContentValues(1);
   1147             values.put(Contacts.STARRED, 0);
   1148             getContentResolver().update(contactUri, values, null, null);
   1149         }
   1150 
   1151         @Override
   1152         public void onDeleteContactAction(Uri contactUri) {
   1153             ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
   1154         }
   1155 
   1156         @Override
   1157         public void onFinishAction() {
   1158             onBackPressed();
   1159         }
   1160 
   1161         @Override
   1162         public void onInvalidSelection() {
   1163             ContactListFilter filter;
   1164             ContactListFilter currentFilter = mAllFragment.getFilter();
   1165             if (currentFilter != null
   1166                     && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
   1167                 filter = ContactListFilter.createFilterWithType(
   1168                         ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
   1169                 mAllFragment.setFilter(filter);
   1170             } else {
   1171                 filter = ContactListFilter.createFilterWithType(
   1172                         ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
   1173                 mAllFragment.setFilter(filter, false);
   1174             }
   1175             mContactListFilterController.setContactListFilter(filter, true);
   1176         }
   1177     }
   1178 
   1179     private class ContactDetailLoaderFragmentListener implements ContactLoaderFragmentListener {
   1180         ContactDetailLoaderFragmentListener() {}
   1181 
   1182         @Override
   1183         public void onContactNotFound() {
   1184             // Nothing needs to be done here
   1185         }
   1186 
   1187         @Override
   1188         public void onDetailsLoaded(final Contact result) {
   1189             if (result == null) {
   1190                 // Nothing is loaded. Show empty state.
   1191                 mContactDetailLayoutController.showEmptyState();
   1192                 return;
   1193             }
   1194             // Since {@link FragmentTransaction}s cannot be done in the onLoadFinished() of the
   1195             // {@link LoaderCallbacks}, then post this {@link Runnable} to the {@link Handler}
   1196             // on the main thread to execute later.
   1197             mHandler.post(new Runnable() {
   1198                 @Override
   1199                 public void run() {
   1200                     // If the activity is destroyed (or will be destroyed soon), don't update the UI
   1201                     if (isFinishing()) {
   1202                         return;
   1203                     }
   1204                     mContactDetailLayoutController.setContactData(result);
   1205                 }
   1206             });
   1207         }
   1208 
   1209         @Override
   1210         public void onEditRequested(Uri contactLookupUri) {
   1211             Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
   1212             intent.putExtra(
   1213                     ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
   1214             startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT);
   1215         }
   1216 
   1217         @Override
   1218         public void onDeleteRequested(Uri contactUri) {
   1219             ContactDeletionInteraction.start(PeopleActivity.this, contactUri, false);
   1220         }
   1221     }
   1222 
   1223     public class ContactDetailFragmentListener implements ContactDetailFragment.Listener {
   1224         @Override
   1225         public void onItemClicked(Intent intent) {
   1226             if (intent == null) {
   1227                 return;
   1228             }
   1229             try {
   1230                 startActivity(intent);
   1231             } catch (ActivityNotFoundException e) {
   1232                 Log.e(TAG, "No activity found for intent: " + intent);
   1233             }
   1234         }
   1235 
   1236         @Override
   1237         public void onCreateRawContactRequested(ArrayList<ContentValues> values,
   1238                 AccountWithDataSet account) {
   1239             Toast.makeText(PeopleActivity.this, R.string.toast_making_personal_copy,
   1240                     Toast.LENGTH_LONG).show();
   1241             Intent serviceIntent = ContactSaveService.createNewRawContactIntent(
   1242                     PeopleActivity.this, values, account,
   1243                     PeopleActivity.class, Intent.ACTION_VIEW);
   1244             startService(serviceIntent);
   1245         }
   1246     }
   1247 
   1248     private class ContactsUnavailableFragmentListener
   1249             implements OnContactsUnavailableActionListener {
   1250         ContactsUnavailableFragmentListener() {}
   1251 
   1252         @Override
   1253         public void onCreateNewContactAction() {
   1254             startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
   1255         }
   1256 
   1257         @Override
   1258         public void onAddAccountAction() {
   1259             Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
   1260             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
   1261             intent.putExtra(Settings.EXTRA_AUTHORITIES,
   1262                     new String[] { ContactsContract.AUTHORITY });
   1263             startActivity(intent);
   1264         }
   1265 
   1266         @Override
   1267         public void onImportContactsFromFileAction() {
   1268             ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
   1269                     PeopleActivity.class);
   1270         }
   1271 
   1272         @Override
   1273         public void onFreeInternalStorageAction() {
   1274             startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
   1275         }
   1276     }
   1277 
   1278     private final class StrequentContactListFragmentListener
   1279             implements ContactTileListFragment.Listener {
   1280         StrequentContactListFragmentListener() {}
   1281 
   1282         @Override
   1283         public void onContactSelected(Uri contactUri, Rect targetRect) {
   1284             if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
   1285                 QuickContact.showQuickContact(PeopleActivity.this, targetRect, contactUri, 0, null);
   1286             } else {
   1287                 startActivity(new Intent(Intent.ACTION_VIEW, contactUri));
   1288             }
   1289         }
   1290 
   1291         @Override
   1292         public void onCallNumberDirectly(String phoneNumber) {
   1293             // No need to call phone number directly from People app.
   1294             Log.w(TAG, "unexpected invocation of onCallNumberDirectly()");
   1295         }
   1296     }
   1297 
   1298     private final class GroupBrowserActionListener implements OnGroupBrowserActionListener {
   1299 
   1300         GroupBrowserActionListener() {}
   1301 
   1302         @Override
   1303         public void onViewGroupAction(Uri groupUri) {
   1304             if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
   1305                 setupGroupDetailFragment(groupUri);
   1306             } else {
   1307                 Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class);
   1308                 intent.setData(groupUri);
   1309                 startActivity(intent);
   1310             }
   1311         }
   1312     }
   1313 
   1314     private class GroupDetailFragmentListener implements GroupDetailFragment.Listener {
   1315 
   1316         GroupDetailFragmentListener() {}
   1317 
   1318         @Override
   1319         public void onGroupSizeUpdated(String size) {
   1320             // Nothing needs to be done here because the size will be displayed in the detail
   1321             // fragment
   1322         }
   1323 
   1324         @Override
   1325         public void onGroupTitleUpdated(String title) {
   1326             // Nothing needs to be done here because the title will be displayed in the detail
   1327             // fragment
   1328         }
   1329 
   1330         @Override
   1331         public void onAccountTypeUpdated(String accountTypeString, String dataSet) {
   1332             // Nothing needs to be done here because the group source will be displayed in the
   1333             // detail fragment
   1334         }
   1335 
   1336         @Override
   1337         public void onEditRequested(Uri groupUri) {
   1338             final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class);
   1339             intent.setData(groupUri);
   1340             intent.setAction(Intent.ACTION_EDIT);
   1341             startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP);
   1342         }
   1343 
   1344         @Override
   1345         public void onContactSelected(Uri contactUri) {
   1346             // Nothing needs to be done here because either quickcontact will be displayed
   1347             // or activity will take care of selection
   1348         }
   1349     }
   1350 
   1351     public void startActivityAndForwardResult(final Intent intent) {
   1352         intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
   1353 
   1354         // Forward extras to the new activity
   1355         Bundle extras = getIntent().getExtras();
   1356         if (extras != null) {
   1357             intent.putExtras(extras);
   1358         }
   1359         startActivity(intent);
   1360         finish();
   1361     }
   1362 
   1363     @Override
   1364     public boolean onCreateOptionsMenu(Menu menu) {
   1365         if (!areContactsAvailable()) {
   1366             // If contacts aren't available, hide all menu items.
   1367             return false;
   1368         }
   1369         super.onCreateOptionsMenu(menu);
   1370 
   1371         MenuInflater inflater = getMenuInflater();
   1372         inflater.inflate(R.menu.people_options, menu);
   1373 
   1374         if (DEBUG_TRANSITIONS && mContactDetailLoaderFragment != null) {
   1375             final MenuItem toggleSocial =
   1376                     menu.add(mContactDetailLoaderFragment.getLoadStreamItems() ? "less" : "more");
   1377             toggleSocial.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
   1378             toggleSocial.setOnMenuItemClickListener(new OnMenuItemClickListener() {
   1379                 @Override
   1380                 public boolean onMenuItemClick(MenuItem item) {
   1381                     mContactDetailLoaderFragment.toggleLoadStreamItems();
   1382                     invalidateOptionsMenu();
   1383                     return false;
   1384                 }
   1385             });
   1386         }
   1387 
   1388         return true;
   1389     }
   1390 
   1391     private void invalidateOptionsMenuIfNeeded() {
   1392         if (isOptionsMenuChanged()) {
   1393             invalidateOptionsMenu();
   1394         }
   1395     }
   1396 
   1397     public boolean isOptionsMenuChanged() {
   1398         if (mOptionsMenuContactsAvailable != areContactsAvailable()) {
   1399             return true;
   1400         }
   1401 
   1402         if (mAllFragment != null && mAllFragment.isOptionsMenuChanged()) {
   1403             return true;
   1404         }
   1405 
   1406         if (mContactDetailLoaderFragment != null &&
   1407                 mContactDetailLoaderFragment.isOptionsMenuChanged()) {
   1408             return true;
   1409         }
   1410 
   1411         if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) {
   1412             return true;
   1413         }
   1414 
   1415         return false;
   1416     }
   1417 
   1418     @Override
   1419     public boolean onPrepareOptionsMenu(Menu menu) {
   1420         mOptionsMenuContactsAvailable = areContactsAvailable();
   1421         if (!mOptionsMenuContactsAvailable) {
   1422             return false;
   1423         }
   1424 
   1425         // Get references to individual menu items in the menu
   1426         final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact);
   1427         final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter);
   1428 
   1429         MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group);
   1430 
   1431         final MenuItem clearFrequentsMenu = menu.findItem(R.id.menu_clear_frequents);
   1432         final MenuItem helpMenu = menu.findItem(R.id.menu_help);
   1433 
   1434         final boolean isSearchMode = mActionBarAdapter.isSearchMode();
   1435         if (isSearchMode) {
   1436             addContactMenu.setVisible(false);
   1437             addGroupMenu.setVisible(false);
   1438             contactsFilterMenu.setVisible(false);
   1439             clearFrequentsMenu.setVisible(false);
   1440             helpMenu.setVisible(false);
   1441         } else {
   1442             switch (mActionBarAdapter.getCurrentTab()) {
   1443                 case TabState.FAVORITES:
   1444                     addContactMenu.setVisible(false);
   1445                     addGroupMenu.setVisible(false);
   1446                     contactsFilterMenu.setVisible(false);
   1447                     clearFrequentsMenu.setVisible(hasFrequents());
   1448                     break;
   1449                 case TabState.ALL:
   1450                     addContactMenu.setVisible(true);
   1451                     addGroupMenu.setVisible(false);
   1452                     contactsFilterMenu.setVisible(true);
   1453                     clearFrequentsMenu.setVisible(false);
   1454                     break;
   1455                 case TabState.GROUPS:
   1456                     // Do not display the "new group" button if no accounts are available
   1457                     if (areGroupWritableAccountsAvailable()) {
   1458                         addGroupMenu.setVisible(true);
   1459                     } else {
   1460                         addGroupMenu.setVisible(false);
   1461                     }
   1462                     addContactMenu.setVisible(false);
   1463                     contactsFilterMenu.setVisible(false);
   1464                     clearFrequentsMenu.setVisible(false);
   1465                     break;
   1466             }
   1467             HelpUtils.prepareHelpMenuItem(this, helpMenu, R.string.help_url_people_main);
   1468         }
   1469         final boolean showMiscOptions = !isSearchMode;
   1470         makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions);
   1471         makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions);
   1472         makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions);
   1473         makeMenuItemVisible(menu, R.id.menu_settings,
   1474                 showMiscOptions && !ContactsPreferenceActivity.isEmpty(this));
   1475 
   1476         // Debug options need to be visible even in search mode.
   1477         makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions);
   1478 
   1479         return true;
   1480     }
   1481 
   1482     /**
   1483      * Returns whether there are any frequently contacted people being displayed
   1484      * @return
   1485      */
   1486     private boolean hasFrequents() {
   1487         if (PhoneCapabilityTester.isUsingTwoPanesInFavorites(this)) {
   1488             return mFrequentFragment.hasFrequents();
   1489         } else {
   1490             return mFavoritesFragment.hasFrequents();
   1491         }
   1492     }
   1493 
   1494     private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
   1495         MenuItem item =menu.findItem(itemId);
   1496         if (item != null) {
   1497             item.setVisible(visible);
   1498         }
   1499     }
   1500 
   1501     @Override
   1502     public boolean onOptionsItemSelected(MenuItem item) {
   1503         switch (item.getItemId()) {
   1504             case android.R.id.home: {
   1505                 // The home icon on the action bar is pressed
   1506                 if (mActionBarAdapter.isUpShowing()) {
   1507                     // "UP" icon press -- should be treated as "back".
   1508                     onBackPressed();
   1509                 }
   1510                 return true;
   1511             }
   1512             case R.id.menu_settings: {
   1513                 final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
   1514                 // as there is only one section right now, make sure it is selected
   1515                 // on small screens, this also hides the section selector
   1516                 // Due to b/5045558, this code unfortunately only works properly on phones
   1517                 boolean settingsAreMultiPane = getResources().getBoolean(
   1518                         com.android.internal.R.bool.preferences_prefer_dual_pane);
   1519                 if (!settingsAreMultiPane) {
   1520                     intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
   1521                             DisplayOptionsPreferenceFragment.class.getName());
   1522                     intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE,
   1523                             R.string.activity_title_settings);
   1524                 }
   1525                 startActivity(intent);
   1526                 return true;
   1527             }
   1528             case R.id.menu_contacts_filter: {
   1529                 AccountFilterUtil.startAccountFilterActivityForResult(
   1530                         this, SUBACTIVITY_ACCOUNT_FILTER,
   1531                         mContactListFilterController.getFilter());
   1532                 return true;
   1533             }
   1534             case R.id.menu_search: {
   1535                 onSearchRequested();
   1536                 return true;
   1537             }
   1538             case R.id.menu_add_contact: {
   1539                 final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
   1540                 // On 2-pane UI, we can let the editor activity finish itself and return
   1541                 // to this activity to display the new contact.
   1542                 if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
   1543                     intent.putExtra(
   1544                             ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED,
   1545                             true);
   1546                     startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT);
   1547                 } else {
   1548                     // Otherwise, on 1-pane UI, we need the editor to launch the view contact
   1549                     // intent itself.
   1550                     startActivity(intent);
   1551                 }
   1552                 return true;
   1553             }
   1554             case R.id.menu_add_group: {
   1555                 createNewGroup();
   1556                 return true;
   1557             }
   1558             case R.id.menu_import_export: {
   1559                 ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
   1560                         PeopleActivity.class);
   1561                 return true;
   1562             }
   1563             case R.id.menu_clear_frequents: {
   1564                 ClearFrequentsDialog.show(getFragmentManager());
   1565                 return true;
   1566             }
   1567             case R.id.menu_accounts: {
   1568                 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
   1569                 intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
   1570                     ContactsContract.AUTHORITY
   1571                 });
   1572                 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
   1573                 startActivity(intent);
   1574                 return true;
   1575             }
   1576             case R.id.export_database: {
   1577                 final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
   1578                 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
   1579                 startActivity(intent);
   1580                 return true;
   1581             }
   1582         }
   1583         return false;
   1584     }
   1585 
   1586     private void createNewGroup() {
   1587         final Intent intent = new Intent(this, GroupEditorActivity.class);
   1588         intent.setAction(Intent.ACTION_INSERT);
   1589         startActivityForResult(intent, SUBACTIVITY_NEW_GROUP);
   1590     }
   1591 
   1592     @Override
   1593     public boolean onSearchRequested() { // Search key pressed.
   1594         mActionBarAdapter.setSearchMode(true);
   1595         return true;
   1596     }
   1597 
   1598     @Override
   1599     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   1600         switch (requestCode) {
   1601             case SUBACTIVITY_ACCOUNT_FILTER: {
   1602                 AccountFilterUtil.handleAccountFilterResult(
   1603                         mContactListFilterController, resultCode, data);
   1604                 break;
   1605             }
   1606 
   1607             case SUBACTIVITY_NEW_CONTACT:
   1608             case SUBACTIVITY_EDIT_CONTACT: {
   1609                 if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
   1610                     mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT);
   1611                     mAllFragment.setSelectionRequired(true);
   1612                     mAllFragment.setSelectedContactUri(data.getData());
   1613                     // Suppress IME if in search mode
   1614                     if (mActionBarAdapter != null) {
   1615                         mActionBarAdapter.clearFocusOnSearchView();
   1616                     }
   1617                     // No need to change the contact filter
   1618                     mCurrentFilterIsValid = true;
   1619                 }
   1620                 break;
   1621             }
   1622 
   1623             case SUBACTIVITY_NEW_GROUP:
   1624             case SUBACTIVITY_EDIT_GROUP: {
   1625                 if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
   1626                     mRequest.setActionCode(ContactsRequest.ACTION_GROUP);
   1627                     mGroupsFragment.setSelectedUri(data.getData());
   1628                 }
   1629                 break;
   1630             }
   1631 
   1632             // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
   1633             // anymore
   1634             case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
   1635                 if (resultCode == RESULT_OK) {
   1636                     mAllFragment.onPickerResult(data);
   1637                 }
   1638 
   1639 // TODO fix or remove multipicker code
   1640 //                else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
   1641 //                    // Finish the activity if the sub activity was canceled as back key is used
   1642 //                    // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
   1643 //                    finish();
   1644 //                }
   1645 //                break;
   1646         }
   1647     }
   1648 
   1649     @Override
   1650     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1651         // TODO move to the fragment
   1652         switch (keyCode) {
   1653 //            case KeyEvent.KEYCODE_CALL: {
   1654 //                if (callSelection()) {
   1655 //                    return true;
   1656 //                }
   1657 //                break;
   1658 //            }
   1659 
   1660             case KeyEvent.KEYCODE_DEL: {
   1661                 if (deleteSelection()) {
   1662                     return true;
   1663                 }
   1664                 break;
   1665             }
   1666             default: {
   1667                 // Bring up the search UI if the user starts typing
   1668                 final int unicodeChar = event.getUnicodeChar();
   1669                 if ((unicodeChar != 0)
   1670                         // If COMBINING_ACCENT is set, it's not a unicode character.
   1671                         && ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0)
   1672                         && !Character.isWhitespace(unicodeChar)) {
   1673                     String query = new String(new int[]{ unicodeChar }, 0, 1);
   1674                     if (!mActionBarAdapter.isSearchMode()) {
   1675                         mActionBarAdapter.setQueryString(query);
   1676                         mActionBarAdapter.setSearchMode(true);
   1677                         return true;
   1678                     }
   1679                 }
   1680             }
   1681         }
   1682 
   1683         return super.onKeyDown(keyCode, event);
   1684     }
   1685 
   1686     @Override
   1687     public void onBackPressed() {
   1688         if (mActionBarAdapter.isSearchMode()) {
   1689             mActionBarAdapter.setSearchMode(false);
   1690         } else {
   1691             super.onBackPressed();
   1692         }
   1693     }
   1694 
   1695     private boolean deleteSelection() {
   1696         // TODO move to the fragment
   1697 //        if (mActionCode == ContactsRequest.ACTION_DEFAULT) {
   1698 //            final int position = mListView.getSelectedItemPosition();
   1699 //            if (position != ListView.INVALID_POSITION) {
   1700 //                Uri contactUri = getContactUri(position);
   1701 //                if (contactUri != null) {
   1702 //                    doContactDelete(contactUri);
   1703 //                    return true;
   1704 //                }
   1705 //            }
   1706 //        }
   1707         return false;
   1708     }
   1709 
   1710     @Override
   1711     protected void onSaveInstanceState(Bundle outState) {
   1712         super.onSaveInstanceState(outState);
   1713         mActionBarAdapter.onSaveInstanceState(outState);
   1714         if (mContactDetailLayoutController != null) {
   1715             mContactDetailLayoutController.onSaveInstanceState(outState);
   1716         }
   1717 
   1718         // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
   1719         // in order to avoid doing fragment transactions after it.
   1720         // TODO Figure out a better way to deal with the issue.
   1721         mActionBarAdapter.setListener(null);
   1722         if (mTabPager != null) {
   1723             mTabPager.setOnPageChangeListener(null);
   1724         }
   1725     }
   1726 
   1727     @Override
   1728     protected void onRestoreInstanceState(Bundle savedInstanceState) {
   1729         super.onRestoreInstanceState(savedInstanceState);
   1730         // In our own lifecycle, the focus is saved and restore but later taken away by the
   1731         // ViewPager. As a hack, we force focus on the SearchView if we know that we are searching.
   1732         // This fixes the keyboard going away on screen rotation
   1733         if (mActionBarAdapter.isSearchMode()) {
   1734             mActionBarAdapter.setFocusOnSearchView();
   1735         }
   1736     }
   1737 
   1738     @Override
   1739     public DialogManager getDialogManager() {
   1740         return mDialogManager;
   1741     }
   1742 
   1743     // Visible for testing
   1744     public ContactBrowseListFragment getListFragment() {
   1745         return mAllFragment;
   1746     }
   1747 
   1748     // Visible for testing
   1749     public ContactDetailFragment getDetailFragment() {
   1750         return mContactDetailFragment;
   1751     }
   1752 }
   1753