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