Home | History | Annotate | Download | only in activities
      1 /*
      2  * Copyright (C) 2008 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.R;
     20 import com.android.contacts.calllog.CallLogFragment;
     21 import com.android.contacts.dialpad.DialpadFragment;
     22 import com.android.contacts.interactions.PhoneNumberInteraction;
     23 import com.android.contacts.list.ContactListFilterController;
     24 import com.android.contacts.list.ContactListFilterController.ContactListFilterListener;
     25 import com.android.contacts.list.ContactListItemView;
     26 import com.android.contacts.list.OnPhoneNumberPickerActionListener;
     27 import com.android.contacts.list.PhoneFavoriteFragment;
     28 import com.android.contacts.list.PhoneNumberPickerFragment;
     29 import com.android.contacts.activities.TransactionSafeActivity;
     30 import com.android.contacts.util.AccountFilterUtil;
     31 import com.android.internal.telephony.ITelephony;
     32 
     33 import android.app.ActionBar;
     34 import android.app.ActionBar.LayoutParams;
     35 import android.app.ActionBar.Tab;
     36 import android.app.ActionBar.TabListener;
     37 import android.app.Activity;
     38 import android.app.Fragment;
     39 import android.app.FragmentManager;
     40 import android.app.FragmentTransaction;
     41 import android.content.Context;
     42 import android.content.Intent;
     43 import android.content.SharedPreferences;
     44 import android.net.Uri;
     45 import android.os.Bundle;
     46 import android.os.RemoteException;
     47 import android.os.ServiceManager;
     48 import android.preference.PreferenceManager;
     49 import android.provider.CallLog.Calls;
     50 import android.provider.ContactsContract.Contacts;
     51 import android.provider.ContactsContract.Intents.UI;
     52 import android.support.v13.app.FragmentPagerAdapter;
     53 import android.support.v4.view.ViewPager;
     54 import android.support.v4.view.ViewPager.OnPageChangeListener;
     55 import android.text.TextUtils;
     56 import android.util.Log;
     57 import android.view.Menu;
     58 import android.view.MenuInflater;
     59 import android.view.MenuItem;
     60 import android.view.MenuItem.OnMenuItemClickListener;
     61 import android.view.View;
     62 import android.view.View.OnClickListener;
     63 import android.view.View.OnFocusChangeListener;
     64 import android.view.ViewConfiguration;
     65 import android.view.inputmethod.InputMethodManager;
     66 import android.widget.PopupMenu;
     67 import android.widget.SearchView;
     68 import android.widget.SearchView.OnCloseListener;
     69 import android.widget.SearchView.OnQueryTextListener;
     70 
     71 /**
     72  * The dialer activity that has one tab with the virtual 12key
     73  * dialer, a tab with recent calls in it, a tab with the contacts and
     74  * a tab with the favorite. This is the container and the tabs are
     75  * embedded using intents.
     76  * The dialer tab's title is 'phone', a more common name (see strings.xml).
     77  */
     78 public class DialtactsActivity extends TransactionSafeActivity {
     79     private static final String TAG = "DialtactsActivity";
     80 
     81     private static final boolean DEBUG = false;
     82 
     83     /** Used to open Call Setting */
     84     private static final String PHONE_PACKAGE = "com.android.phone";
     85     private static final String CALL_SETTINGS_CLASS_NAME =
     86             "com.android.phone.CallFeaturesSetting";
     87 
     88     /**
     89      * Copied from PhoneApp. See comments in Phone app for more detail.
     90      */
     91     public static final String EXTRA_CALL_ORIGIN = "com.android.phone.CALL_ORIGIN";
     92     public static final String CALL_ORIGIN_DIALTACTS =
     93             "com.android.contacts.activities.DialtactsActivity";
     94 
     95     /**
     96      * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
     97      */
     98     private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
     99 
    100     /** Used both by {@link ActionBar} and {@link ViewPagerAdapter} */
    101     private static final int TAB_INDEX_DIALER = 0;
    102     private static final int TAB_INDEX_CALL_LOG = 1;
    103     private static final int TAB_INDEX_FAVORITES = 2;
    104 
    105     private static final int TAB_INDEX_COUNT = 3;
    106 
    107     private SharedPreferences mPrefs;
    108 
    109     /** Last manually selected tab index */
    110     private static final String PREF_LAST_MANUALLY_SELECTED_TAB =
    111             "DialtactsActivity_last_manually_selected_tab";
    112     private static final int PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT = TAB_INDEX_DIALER;
    113 
    114     private static final int SUBACTIVITY_ACCOUNT_FILTER = 1;
    115 
    116     /**
    117      * Listener interface for Fragments accommodated in {@link ViewPager} enabling them to know
    118      * when it becomes visible or invisible inside the ViewPager.
    119      */
    120     public interface ViewPagerVisibilityListener {
    121         public void onVisibilityChanged(boolean visible);
    122     }
    123 
    124     public class ViewPagerAdapter extends FragmentPagerAdapter {
    125         public ViewPagerAdapter(FragmentManager fm) {
    126             super(fm);
    127         }
    128 
    129         @Override
    130         public Fragment getItem(int position) {
    131             switch (position) {
    132                 case TAB_INDEX_DIALER:
    133                     return new DialpadFragment();
    134                 case TAB_INDEX_CALL_LOG:
    135                     return new CallLogFragment();
    136                 case TAB_INDEX_FAVORITES:
    137                     return new PhoneFavoriteFragment();
    138             }
    139             throw new IllegalStateException("No fragment at position " + position);
    140         }
    141 
    142         @Override
    143         public int getCount() {
    144             return TAB_INDEX_COUNT;
    145         }
    146     }
    147 
    148     /**
    149      * True when the app detects user's drag event. This variable should not become true when
    150      * mUserTabClick is true.
    151      *
    152      * During user's drag or tab click, we shouldn't show fake buttons but just show real
    153      * ActionBar at the bottom of the screen, for transition animation.
    154      */
    155     boolean mDuringSwipe = false;
    156     /**
    157      * True when the app detects user's tab click (at the top of the screen). This variable should
    158      * not become true when mDuringSwipe is true.
    159      *
    160      * During user's drag or tab click, we shouldn't show fake buttons but just show real
    161      * ActionBar at the bottom of the screen, for transition animation.
    162      */
    163     boolean mUserTabClick = false;
    164 
    165     private class PageChangeListener implements OnPageChangeListener {
    166         private int mCurrentPosition = -1;
    167         /**
    168          * Used during page migration, to remember the next position {@link #onPageSelected(int)}
    169          * specified.
    170          */
    171         private int mNextPosition = -1;
    172 
    173         @Override
    174         public void onPageScrolled(
    175                 int position, float positionOffset, int positionOffsetPixels) {
    176         }
    177 
    178         @Override
    179         public void onPageSelected(int position) {
    180             if (DEBUG) Log.d(TAG, "onPageSelected: " + position);
    181             final ActionBar actionBar = getActionBar();
    182             if (mDialpadFragment != null && !mDuringSwipe) {
    183                 if (DEBUG) {
    184                     Log.d(TAG, "Immediately show/hide fake menu buttons. position: "
    185                             + position + ", dragging: " + mDuringSwipe);
    186                 }
    187                 mDialpadFragment.updateFakeMenuButtonsVisibility(
    188                         position == TAB_INDEX_DIALER && !mDuringSwipe);
    189             }
    190 
    191             if (mCurrentPosition == position) {
    192                 Log.w(TAG, "Previous position and next position became same (" + position + ")");
    193             }
    194 
    195             actionBar.selectTab(actionBar.getTabAt(position));
    196             mNextPosition = position;
    197         }
    198 
    199         public void setCurrentPosition(int position) {
    200             mCurrentPosition = position;
    201         }
    202 
    203         @Override
    204         public void onPageScrollStateChanged(int state) {
    205             switch (state) {
    206                 case ViewPager.SCROLL_STATE_IDLE: {
    207                     if (DEBUG) Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_IDLE");
    208                     // Interpret IDLE as the end of migration (both swipe and tab click)
    209                     mDuringSwipe = false;
    210                     mUserTabClick = false;
    211 
    212                     if (mCurrentPosition >= 0) {
    213                         sendFragmentVisibilityChange(mCurrentPosition, false);
    214                     }
    215                     if (mNextPosition >= 0) {
    216                         sendFragmentVisibilityChange(mNextPosition, true);
    217                     }
    218                     invalidateOptionsMenu();
    219 
    220                     mCurrentPosition = mNextPosition;
    221                     break;
    222                 }
    223                 case ViewPager.SCROLL_STATE_DRAGGING: {
    224                     if (DEBUG) Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_DRAGGING");
    225                     mDuringSwipe = true;
    226                     mUserTabClick = false;
    227 
    228                     if (mCurrentPosition == TAB_INDEX_DIALER) {
    229                         sendFragmentVisibilityChange(TAB_INDEX_DIALER, false);
    230                         sendFragmentVisibilityChange(TAB_INDEX_CALL_LOG, true);
    231                         invalidateOptionsMenu();
    232                     }
    233                     break;
    234                 }
    235                 case ViewPager.SCROLL_STATE_SETTLING: {
    236                     if (DEBUG) Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_SETTLING");
    237                     mDuringSwipe = true;
    238                     mUserTabClick = false;
    239                     break;
    240                 }
    241                 default:
    242                     break;
    243             }
    244         }
    245     }
    246 
    247     private String mFilterText;
    248 
    249     /** Enables horizontal swipe between Fragments. */
    250     private ViewPager mViewPager;
    251     private final PageChangeListener mPageChangeListener = new PageChangeListener();
    252     private DialpadFragment mDialpadFragment;
    253     private CallLogFragment mCallLogFragment;
    254     private PhoneFavoriteFragment mPhoneFavoriteFragment;
    255 
    256     private final ContactListFilterListener mContactListFilterListener =
    257             new ContactListFilterListener() {
    258         @Override
    259         public void onContactListFilterChanged() {
    260             boolean doInvalidateOptionsMenu = false;
    261 
    262             if (mPhoneFavoriteFragment != null && mPhoneFavoriteFragment.isAdded()) {
    263                 mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
    264                 doInvalidateOptionsMenu = true;
    265             }
    266 
    267             if (mSearchFragment != null && mSearchFragment.isAdded()) {
    268                 mSearchFragment.setFilter(mContactListFilterController.getFilter());
    269                 doInvalidateOptionsMenu = true;
    270             } else {
    271                 Log.w(TAG, "Search Fragment isn't available when ContactListFilter is changed");
    272             }
    273 
    274             if (doInvalidateOptionsMenu) {
    275                 invalidateOptionsMenu();
    276             }
    277         }
    278     };
    279 
    280     private final TabListener mTabListener = new TabListener() {
    281         @Override
    282         public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    283             if (DEBUG) Log.d(TAG, "onTabUnselected(). tab: " + tab);
    284         }
    285 
    286         @Override
    287         public void onTabSelected(Tab tab, FragmentTransaction ft) {
    288             if (DEBUG) {
    289                 Log.d(TAG, "onTabSelected(). tab: " + tab + ", mDuringSwipe: " + mDuringSwipe);
    290             }
    291             // When the user swipes the screen horizontally, this method will be called after
    292             // ViewPager.SCROLL_STATE_DRAGGING and ViewPager.SCROLL_STATE_SETTLING events, while
    293             // when the user clicks a tab at the ActionBar at the top, this will be called before
    294             // them. This logic interprets the order difference as a difference of the user action.
    295             if (!mDuringSwipe) {
    296                 if (mDialpadFragment != null) {
    297                     if (DEBUG) Log.d(TAG, "Immediately hide fake buttons for tab selection case");
    298                     mDialpadFragment.updateFakeMenuButtonsVisibility(false);
    299                 }
    300                 mUserTabClick = true;
    301             }
    302 
    303             if (mViewPager.getCurrentItem() != tab.getPosition()) {
    304                 mViewPager.setCurrentItem(tab.getPosition(), true);
    305             }
    306 
    307             // During the call, we don't remember the tab position.
    308             if (!DialpadFragment.phoneIsInUse()) {
    309                 // Remember this tab index. This function is also called, if the tab is set
    310                 // automatically in which case the setter (setCurrentTab) has to set this to its old
    311                 // value afterwards
    312                 mLastManuallySelectedFragment = tab.getPosition();
    313             }
    314         }
    315 
    316         @Override
    317         public void onTabReselected(Tab tab, FragmentTransaction ft) {
    318             if (DEBUG) Log.d(TAG, "onTabReselected");
    319         }
    320     };
    321 
    322     /**
    323      * Fragment for searching phone numbers. Unlike the other Fragments, this doesn't correspond
    324      * to tab but is shown by a search action.
    325      */
    326     private PhoneNumberPickerFragment mSearchFragment;
    327     /**
    328      * True when this Activity is in its search UI (with a {@link SearchView} and
    329      * {@link PhoneNumberPickerFragment}).
    330      */
    331     private boolean mInSearchUi;
    332     private SearchView mSearchView;
    333 
    334     private final OnClickListener mFilterOptionClickListener = new OnClickListener() {
    335         @Override
    336         public void onClick(View view) {
    337             final PopupMenu popupMenu = new PopupMenu(DialtactsActivity.this, view);
    338             final Menu menu = popupMenu.getMenu();
    339             popupMenu.inflate(R.menu.dialtacts_search_options);
    340             final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
    341             filterOptionMenuItem.setOnMenuItemClickListener(mFilterOptionsMenuItemClickListener);
    342             final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
    343             addContactOptionMenuItem.setIntent(
    344                     new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
    345             popupMenu.show();
    346         }
    347     };
    348 
    349     /**
    350      * The index of the Fragment (or, the tab) that has last been manually selected.
    351      * This value does not keep track of programmatically set Tabs (e.g. Call Log after a Call)
    352      */
    353     private int mLastManuallySelectedFragment;
    354 
    355     private ContactListFilterController mContactListFilterController;
    356     private OnMenuItemClickListener mFilterOptionsMenuItemClickListener =
    357             new OnMenuItemClickListener() {
    358         @Override
    359         public boolean onMenuItemClick(MenuItem item) {
    360             AccountFilterUtil.startAccountFilterActivityForResult(
    361                     DialtactsActivity.this, SUBACTIVITY_ACCOUNT_FILTER);
    362             return true;
    363         }
    364     };
    365 
    366     private OnMenuItemClickListener mSearchMenuItemClickListener =
    367             new OnMenuItemClickListener() {
    368         @Override
    369         public boolean onMenuItemClick(MenuItem item) {
    370             enterSearchUi();
    371             return true;
    372         }
    373     };
    374 
    375     /**
    376      * Listener used when one of phone numbers in search UI is selected. This will initiate a
    377      * phone call using the phone number.
    378      */
    379     private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener =
    380             new OnPhoneNumberPickerActionListener() {
    381                 @Override
    382                 public void onPickPhoneNumberAction(Uri dataUri) {
    383                     // Specify call-origin so that users will see the previous tab instead of
    384                     // CallLog screen (search UI will be automatically exited).
    385                     PhoneNumberInteraction.startInteractionForPhoneCall(
    386                             DialtactsActivity.this, dataUri,
    387                             CALL_ORIGIN_DIALTACTS);
    388                 }
    389 
    390                 @Override
    391                 public void onShortcutIntentCreated(Intent intent) {
    392                     Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
    393                 }
    394 
    395                 @Override
    396                 public void onHomeInActionBarSelected() {
    397                     exitSearchUi();
    398                 }
    399     };
    400 
    401     /**
    402      * Listener used to send search queries to the phone search fragment.
    403      */
    404     private final OnQueryTextListener mPhoneSearchQueryTextListener =
    405             new OnQueryTextListener() {
    406                 @Override
    407                 public boolean onQueryTextSubmit(String query) {
    408                     View view = getCurrentFocus();
    409                     if (view != null) {
    410                         hideInputMethod(view);
    411                         view.clearFocus();
    412                     }
    413                     return true;
    414                 }
    415 
    416                 @Override
    417                 public boolean onQueryTextChange(String newText) {
    418                     // Show search result with non-empty text. Show a bare list otherwise.
    419                     if (mSearchFragment != null) {
    420                         mSearchFragment.setQueryString(newText, true);
    421                     }
    422                     return true;
    423                 }
    424     };
    425 
    426     /**
    427      * Listener used to handle the "close" button on the right side of {@link SearchView}.
    428      * If some text is in the search view, this will clean it up. Otherwise this will exit
    429      * the search UI and let users go back to usual Phone UI.
    430      *
    431      * This does _not_ handle back button.
    432      */
    433     private final OnCloseListener mPhoneSearchCloseListener =
    434             new OnCloseListener() {
    435                 @Override
    436                 public boolean onClose() {
    437                     if (!TextUtils.isEmpty(mSearchView.getQuery())) {
    438                         mSearchView.setQuery(null, true);
    439                     }
    440                     return true;
    441                 }
    442     };
    443 
    444     private final View.OnLayoutChangeListener mFirstLayoutListener
    445             = new View.OnLayoutChangeListener() {
    446         @Override
    447         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
    448                 int oldTop, int oldRight, int oldBottom) {
    449             v.removeOnLayoutChangeListener(this); // Unregister self.
    450             addSearchFragment();
    451         }
    452     };
    453 
    454     @Override
    455     protected void onCreate(Bundle icicle) {
    456         super.onCreate(icicle);
    457 
    458         final Intent intent = getIntent();
    459         fixIntent(intent);
    460 
    461         setContentView(R.layout.dialtacts_activity);
    462 
    463         mContactListFilterController = ContactListFilterController.getInstance(this);
    464         mContactListFilterController.addListener(mContactListFilterListener);
    465 
    466         findViewById(R.id.dialtacts_frame).addOnLayoutChangeListener(mFirstLayoutListener);
    467 
    468         mViewPager = (ViewPager) findViewById(R.id.pager);
    469         mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
    470         mViewPager.setOnPageChangeListener(mPageChangeListener);
    471 
    472         // Setup the ActionBar tabs (the order matches the tab-index contants TAB_INDEX_*)
    473         setupDialer();
    474         setupCallLog();
    475         setupFavorites();
    476         getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    477         getActionBar().setDisplayShowTitleEnabled(false);
    478         getActionBar().setDisplayShowHomeEnabled(false);
    479 
    480         // Load the last manually loaded tab
    481         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    482         mLastManuallySelectedFragment = mPrefs.getInt(PREF_LAST_MANUALLY_SELECTED_TAB,
    483                 PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT);
    484         if (mLastManuallySelectedFragment >= TAB_INDEX_COUNT) {
    485             // Stored value may have exceeded the number of current tabs. Reset it.
    486             mLastManuallySelectedFragment = PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT;
    487         }
    488 
    489         setCurrentTab(intent);
    490 
    491         if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
    492                 && icicle == null) {
    493             setupFilterText(intent);
    494         }
    495     }
    496 
    497     @Override
    498     public void onStart() {
    499         super.onStart();
    500         if (mPhoneFavoriteFragment != null) {
    501             mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
    502         }
    503         if (mSearchFragment != null) {
    504             mSearchFragment.setFilter(mContactListFilterController.getFilter());
    505         }
    506 
    507         if (mDuringSwipe || mUserTabClick) {
    508             if (DEBUG) Log.d(TAG, "reset buggy flag state..");
    509             mDuringSwipe = false;
    510             mUserTabClick = false;
    511         }
    512     }
    513 
    514     @Override
    515     public void onDestroy() {
    516         super.onDestroy();
    517         mContactListFilterController.removeListener(mContactListFilterListener);
    518     }
    519 
    520     /**
    521      * Add search fragment.  Note this is called during onLayout, so there's some restrictions,
    522      * such as executePendingTransaction can't be used in it.
    523      */
    524     private void addSearchFragment() {
    525         // In order to take full advantage of "fragment deferred start", we need to create the
    526         // search fragment after all other fragments are created.
    527         // The other fragments are created by the ViewPager on the first onMeasure().
    528         // We use the first onLayout call, which is after onMeasure().
    529 
    530         // Just return if the fragment is already created, which happens after configuration
    531         // changes.
    532         if (mSearchFragment != null) return;
    533 
    534         final FragmentTransaction ft = getFragmentManager().beginTransaction();
    535         final Fragment searchFragment = new PhoneNumberPickerFragment();
    536 
    537         searchFragment.setUserVisibleHint(false);
    538         ft.add(R.id.dialtacts_frame, searchFragment);
    539         ft.hide(searchFragment);
    540         ft.commitAllowingStateLoss();
    541     }
    542 
    543     private void prepareSearchView() {
    544         final View searchViewLayout =
    545                 getLayoutInflater().inflate(R.layout.dialtacts_custom_action_bar, null);
    546         mSearchView = (SearchView) searchViewLayout.findViewById(R.id.search_view);
    547         mSearchView.setOnQueryTextListener(mPhoneSearchQueryTextListener);
    548         mSearchView.setOnCloseListener(mPhoneSearchCloseListener);
    549         // Since we're using a custom layout for showing SearchView instead of letting the
    550         // search menu icon do that job, we need to manually configure the View so it looks
    551         // "shown via search menu".
    552         // - it should be iconified by default
    553         // - it should not be iconified at this time
    554         // See also comments for onActionViewExpanded()/onActionViewCollapsed()
    555         mSearchView.setIconifiedByDefault(true);
    556         mSearchView.setQueryHint(getString(R.string.hint_findContacts));
    557         mSearchView.setIconified(false);
    558         mSearchView.setOnQueryTextFocusChangeListener(new OnFocusChangeListener() {
    559             @Override
    560             public void onFocusChange(View view, boolean hasFocus) {
    561                 if (hasFocus) {
    562                     showInputMethod(view.findFocus());
    563                 }
    564             }
    565         });
    566 
    567         if (!ViewConfiguration.get(this).hasPermanentMenuKey()) {
    568             // Filter option menu should be shown on the right side of SearchView.
    569             final View filterOptionView = searchViewLayout.findViewById(R.id.search_option);
    570             filterOptionView.setVisibility(View.VISIBLE);
    571             filterOptionView.setOnClickListener(mFilterOptionClickListener);
    572         }
    573 
    574         getActionBar().setCustomView(searchViewLayout,
    575                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    576     }
    577 
    578     @Override
    579     public void onAttachFragment(Fragment fragment) {
    580         // This method can be called before onCreate(), at which point we cannot rely on ViewPager.
    581         // In that case, we will setup the "current position" soon after the ViewPager is ready.
    582         final int currentPosition = mViewPager != null ? mViewPager.getCurrentItem() : -1;
    583 
    584         if (fragment instanceof DialpadFragment) {
    585             mDialpadFragment = (DialpadFragment) fragment;
    586             mDialpadFragment.setListener(mDialpadListener);
    587             if (currentPosition == TAB_INDEX_DIALER) {
    588                 mDialpadFragment.onVisibilityChanged(true);
    589             }
    590         } else if (fragment instanceof CallLogFragment) {
    591             mCallLogFragment = (CallLogFragment) fragment;
    592             if (currentPosition == TAB_INDEX_CALL_LOG) {
    593                 mCallLogFragment.onVisibilityChanged(true);
    594             }
    595         } else if (fragment instanceof PhoneFavoriteFragment) {
    596             mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment;
    597             mPhoneFavoriteFragment.setListener(mPhoneFavoriteListener);
    598             if (mContactListFilterController != null
    599                     && mContactListFilterController.getFilter() != null) {
    600                 mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
    601             }
    602         } else if (fragment instanceof PhoneNumberPickerFragment) {
    603             mSearchFragment = (PhoneNumberPickerFragment) fragment;
    604             mSearchFragment.setOnPhoneNumberPickerActionListener(mPhoneNumberPickerActionListener);
    605             mSearchFragment.setQuickContactEnabled(true);
    606             mSearchFragment.setDarkTheme(true);
    607             mSearchFragment.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT);
    608             if (mContactListFilterController != null
    609                     && mContactListFilterController.getFilter() != null) {
    610                 mSearchFragment.setFilter(mContactListFilterController.getFilter());
    611             }
    612             // Here we assume that we're not on the search mode, so let's hide the fragment.
    613             //
    614             // We get here either when the fragment is created (normal case), or after configuration
    615             // changes.  In the former case, we're not in search mode because we can only
    616             // enter search mode if the fragment is created.  (see enterSearchUi())
    617             // In the latter case we're not in search mode either because we don't retain
    618             // mInSearchUi -- ideally we should but at this point it's not supported.
    619             mSearchFragment.setUserVisibleHint(false);
    620             // After configuration changes fragments will forget their "hidden" state, so make
    621             // sure to hide it.
    622             if (!mSearchFragment.isHidden()) {
    623                 final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    624                 transaction.hide(mSearchFragment);
    625                 transaction.commitAllowingStateLoss();
    626             }
    627         }
    628     }
    629 
    630     @Override
    631     protected void onPause() {
    632         super.onPause();
    633 
    634         mPrefs.edit().putInt(PREF_LAST_MANUALLY_SELECTED_TAB, mLastManuallySelectedFragment)
    635                 .apply();
    636     }
    637 
    638     private void fixIntent(Intent intent) {
    639         // This should be cleaned up: the call key used to send an Intent
    640         // that just said to go to the recent calls list.  It now sends this
    641         // abstract action, but this class hasn't been rewritten to deal with it.
    642         if (Intent.ACTION_CALL_BUTTON.equals(intent.getAction())) {
    643             intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE);
    644             intent.putExtra("call_key", true);
    645             setIntent(intent);
    646         }
    647     }
    648 
    649     private void setupDialer() {
    650         final Tab tab = getActionBar().newTab();
    651         tab.setContentDescription(R.string.dialerIconLabel);
    652         tab.setTabListener(mTabListener);
    653         tab.setIcon(R.drawable.ic_tab_dialer);
    654         getActionBar().addTab(tab);
    655     }
    656 
    657     private void setupCallLog() {
    658         final Tab tab = getActionBar().newTab();
    659         tab.setContentDescription(R.string.recentCallsIconLabel);
    660         tab.setIcon(R.drawable.ic_tab_recent);
    661         tab.setTabListener(mTabListener);
    662         getActionBar().addTab(tab);
    663     }
    664 
    665     private void setupFavorites() {
    666         final Tab tab = getActionBar().newTab();
    667         tab.setContentDescription(R.string.contactsFavoritesLabel);
    668         tab.setIcon(R.drawable.ic_tab_all);
    669         tab.setTabListener(mTabListener);
    670         getActionBar().addTab(tab);
    671     }
    672 
    673     /**
    674      * Returns true if the intent is due to hitting the green send key while in a call.
    675      *
    676      * @param intent the intent that launched this activity
    677      * @param recentCallsRequest true if the intent is requesting to view recent calls
    678      * @return true if the intent is due to hitting the green send key while in a call
    679      */
    680     private boolean isSendKeyWhileInCall(final Intent intent,
    681             final boolean recentCallsRequest) {
    682         // If there is a call in progress go to the call screen
    683         if (recentCallsRequest) {
    684             final boolean callKey = intent.getBooleanExtra("call_key", false);
    685 
    686             try {
    687                 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
    688                 if (callKey && phone != null && phone.showCallScreen()) {
    689                     return true;
    690                 }
    691             } catch (RemoteException e) {
    692                 Log.e(TAG, "Failed to handle send while in call", e);
    693             }
    694         }
    695 
    696         return false;
    697     }
    698 
    699     /**
    700      * Sets the current tab based on the intent's request type
    701      *
    702      * @param intent Intent that contains information about which tab should be selected
    703      */
    704     private void setCurrentTab(Intent intent) {
    705         // If we got here by hitting send and we're in call forward along to the in-call activity
    706         final boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.getType());
    707         if (isSendKeyWhileInCall(intent, recentCallsRequest)) {
    708             finish();
    709             return;
    710         }
    711 
    712         // Remember the old manually selected tab index so that it can be restored if it is
    713         // overwritten by one of the programmatic tab selections
    714         final int savedTabIndex = mLastManuallySelectedFragment;
    715 
    716         final int tabIndex;
    717         if (DialpadFragment.phoneIsInUse() || isDialIntent(intent)) {
    718             tabIndex = TAB_INDEX_DIALER;
    719         } else if (recentCallsRequest) {
    720             tabIndex = TAB_INDEX_CALL_LOG;
    721         } else {
    722             tabIndex = mLastManuallySelectedFragment;
    723         }
    724 
    725         final int previousItemIndex = mViewPager.getCurrentItem();
    726         mViewPager.setCurrentItem(tabIndex, false /* smoothScroll */);
    727         if (previousItemIndex != tabIndex) {
    728             sendFragmentVisibilityChange(previousItemIndex, false);
    729         }
    730         mPageChangeListener.setCurrentPosition(tabIndex);
    731         sendFragmentVisibilityChange(tabIndex, true);
    732 
    733         // Restore to the previous manual selection
    734         mLastManuallySelectedFragment = savedTabIndex;
    735         mDuringSwipe = false;
    736         mUserTabClick = false;
    737     }
    738 
    739     @Override
    740     public void onNewIntent(Intent newIntent) {
    741         setIntent(newIntent);
    742         fixIntent(newIntent);
    743         setCurrentTab(newIntent);
    744         final String action = newIntent.getAction();
    745         if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
    746             setupFilterText(newIntent);
    747         }
    748         if (mInSearchUi || (mSearchFragment != null && mSearchFragment.isVisible())) {
    749             exitSearchUi();
    750         }
    751 
    752         if (mViewPager.getCurrentItem() == TAB_INDEX_DIALER) {
    753             if (mDialpadFragment != null) {
    754                 mDialpadFragment.configureScreenFromIntent(newIntent);
    755             } else {
    756                 Log.e(TAG, "DialpadFragment isn't ready yet when the tab is already selected.");
    757             }
    758         }
    759         invalidateOptionsMenu();
    760     }
    761 
    762     /** Returns true if the given intent contains a phone number to populate the dialer with */
    763     private boolean isDialIntent(Intent intent) {
    764         final String action = intent.getAction();
    765         if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
    766             return true;
    767         }
    768         if (Intent.ACTION_VIEW.equals(action)) {
    769             final Uri data = intent.getData();
    770             if (data != null && "tel".equals(data.getScheme())) {
    771                 return true;
    772             }
    773         }
    774         return false;
    775     }
    776 
    777     /**
    778      * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
    779      * This text originally came from a FILTER_CONTACTS_ACTION intent received
    780      * by this activity. The stored text will then be cleared after after this
    781      * method returns.
    782      *
    783      * @return The stored filter text
    784      */
    785     public String getAndClearFilterText() {
    786         String filterText = mFilterText;
    787         mFilterText = null;
    788         return filterText;
    789     }
    790 
    791     /**
    792      * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
    793      * This is so child activities can check if they are supposed to display a filter.
    794      *
    795      * @param intent The intent received in {@link #onNewIntent(Intent)}
    796      */
    797     private void setupFilterText(Intent intent) {
    798         // If the intent was relaunched from history, don't apply the filter text.
    799         if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
    800             return;
    801         }
    802         String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
    803         if (filter != null && filter.length() > 0) {
    804             mFilterText = filter;
    805         }
    806     }
    807 
    808     @Override
    809     public void onBackPressed() {
    810         if (mInSearchUi) {
    811             // We should let the user go back to usual screens with tabs.
    812             exitSearchUi();
    813         } else if (isTaskRoot()) {
    814             // Instead of stopping, simply push this to the back of the stack.
    815             // This is only done when running at the top of the stack;
    816             // otherwise, we have been launched by someone else so need to
    817             // allow the user to go back to the caller.
    818             moveTaskToBack(false);
    819         } else {
    820             super.onBackPressed();
    821         }
    822     }
    823 
    824     private DialpadFragment.Listener mDialpadListener = new DialpadFragment.Listener() {
    825         @Override
    826         public void onSearchButtonPressed() {
    827             enterSearchUi();
    828         }
    829     };
    830 
    831     private PhoneFavoriteFragment.Listener mPhoneFavoriteListener =
    832             new PhoneFavoriteFragment.Listener() {
    833         @Override
    834         public void onContactSelected(Uri contactUri) {
    835             PhoneNumberInteraction.startInteractionForPhoneCall(
    836                     DialtactsActivity.this, contactUri,
    837                     CALL_ORIGIN_DIALTACTS);
    838         }
    839     };
    840 
    841     @Override
    842     public boolean onCreateOptionsMenu(Menu menu) {
    843         MenuInflater inflater = getMenuInflater();
    844         inflater.inflate(R.menu.dialtacts_options, menu);
    845         return true;
    846     }
    847 
    848     @Override
    849     public boolean onPrepareOptionsMenu(Menu menu) {
    850         final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
    851         final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
    852         final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
    853         final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
    854         final MenuItem fakeMenuItem = menu.findItem(R.id.fake_menu_item);
    855         Tab tab = getActionBar().getSelectedTab();
    856         if (mInSearchUi) {
    857             searchMenuItem.setVisible(false);
    858             if (ViewConfiguration.get(this).hasPermanentMenuKey()) {
    859                 filterOptionMenuItem.setVisible(true);
    860                 filterOptionMenuItem.setOnMenuItemClickListener(
    861                         mFilterOptionsMenuItemClickListener);
    862                 addContactOptionMenuItem.setVisible(true);
    863                 addContactOptionMenuItem.setIntent(
    864                         new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
    865             } else {
    866                 // Filter option menu should be not be shown as a overflow menu.
    867                 filterOptionMenuItem.setVisible(false);
    868                 addContactOptionMenuItem.setVisible(false);
    869             }
    870             callSettingsMenuItem.setVisible(false);
    871             fakeMenuItem.setVisible(false);
    872         } else {
    873             final boolean showCallSettingsMenu;
    874             if (tab != null && tab.getPosition() == TAB_INDEX_DIALER) {
    875                 if (DEBUG) {
    876                     Log.d(TAG, "onPrepareOptionsMenu(dialer). swipe: " + mDuringSwipe
    877                             + ", user tab click: " + mUserTabClick);
    878                 }
    879                 if (mDuringSwipe || mUserTabClick) {
    880                     // During horizontal movement, we just show real ActionBar menu items.
    881                     searchMenuItem.setVisible(true);
    882                     searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
    883                     showCallSettingsMenu = true;
    884 
    885                     fakeMenuItem.setVisible(ViewConfiguration.get(this).hasPermanentMenuKey());
    886                 } else {
    887                     searchMenuItem.setVisible(false);
    888                     // When permanent menu key is _not_ available, the call settings menu should be
    889                     // available via DialpadFragment.
    890                     showCallSettingsMenu = ViewConfiguration.get(this).hasPermanentMenuKey();
    891                     fakeMenuItem.setVisible(false);
    892                 }
    893             } else {
    894                 searchMenuItem.setVisible(true);
    895                 searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
    896                 showCallSettingsMenu = true;
    897                 fakeMenuItem.setVisible(ViewConfiguration.get(this).hasPermanentMenuKey());
    898             }
    899             if (tab != null && tab.getPosition() == TAB_INDEX_FAVORITES) {
    900                 filterOptionMenuItem.setVisible(true);
    901                 filterOptionMenuItem.setOnMenuItemClickListener(
    902                         mFilterOptionsMenuItemClickListener);
    903                 addContactOptionMenuItem.setVisible(true);
    904                 addContactOptionMenuItem.setIntent(
    905                         new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
    906             } else {
    907                 filterOptionMenuItem.setVisible(false);
    908                 addContactOptionMenuItem.setVisible(false);
    909             }
    910 
    911             if (showCallSettingsMenu) {
    912                 callSettingsMenuItem.setVisible(true);
    913                 callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
    914             } else {
    915                 callSettingsMenuItem.setVisible(false);
    916             }
    917         }
    918 
    919         return true;
    920     }
    921 
    922     @Override
    923     public void startSearch(String initialQuery, boolean selectInitialQuery,
    924             Bundle appSearchData, boolean globalSearch) {
    925         if (mSearchFragment != null && mSearchFragment.isAdded() && !globalSearch) {
    926             if (mInSearchUi) {
    927                 if (mSearchView.hasFocus()) {
    928                     showInputMethod(mSearchView.findFocus());
    929                 } else {
    930                     mSearchView.requestFocus();
    931                 }
    932             } else {
    933                 enterSearchUi();
    934             }
    935         } else {
    936             super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
    937         }
    938     }
    939 
    940     /**
    941      * Hides every tab and shows search UI for phone lookup.
    942      */
    943     private void enterSearchUi() {
    944         if (mSearchFragment == null) {
    945             // We add the search fragment dynamically in the first onLayoutChange() and
    946             // mSearchFragment is set sometime later when the fragment transaction is actually
    947             // executed, which means there's a window when users are able to hit the (physical)
    948             // search key but mSearchFragment is still null.
    949             // It's quite hard to handle this case right, so let's just ignore the search key
    950             // in this case.  Users can just hit it again and it will work this time.
    951             return;
    952         }
    953         if (mSearchView == null) {
    954             prepareSearchView();
    955         }
    956 
    957         final ActionBar actionBar = getActionBar();
    958 
    959         final Tab tab = actionBar.getSelectedTab();
    960 
    961         // User can search during the call, but we don't want to remember the status.
    962         if (tab != null && !DialpadFragment.phoneIsInUse()) {
    963             mLastManuallySelectedFragment = tab.getPosition();
    964         }
    965 
    966         mSearchView.setQuery(null, true);
    967 
    968         actionBar.setDisplayShowCustomEnabled(true);
    969         actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
    970         actionBar.setDisplayShowHomeEnabled(true);
    971         actionBar.setDisplayHomeAsUpEnabled(true);
    972 
    973         sendFragmentVisibilityChange(mViewPager.getCurrentItem(), false);
    974 
    975         // Show the search fragment and hide everything else.
    976         mSearchFragment.setUserVisibleHint(true);
    977         final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    978         transaction.show(mSearchFragment);
    979         transaction.commitAllowingStateLoss();
    980         mViewPager.setVisibility(View.GONE);
    981 
    982         // We need to call this and onActionViewCollapsed() manually, since we are using a custom
    983         // layout instead of asking the search menu item to take care of SearchView.
    984         mSearchView.onActionViewExpanded();
    985         mInSearchUi = true;
    986     }
    987 
    988     private void showInputMethod(View view) {
    989         InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    990         if (imm != null) {
    991             if (!imm.showSoftInput(view, 0)) {
    992                 Log.w(TAG, "Failed to show soft input method.");
    993             }
    994         }
    995     }
    996 
    997     private void hideInputMethod(View view) {
    998         InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    999         if (imm != null && view != null) {
   1000             imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
   1001         }
   1002     }
   1003 
   1004     /**
   1005      * Goes back to usual Phone UI with tags. Previously selected Tag and associated Fragment
   1006      * should be automatically focused again.
   1007      */
   1008     private void exitSearchUi() {
   1009         final ActionBar actionBar = getActionBar();
   1010 
   1011         // Hide the search fragment, if exists.
   1012         if (mSearchFragment != null) {
   1013             mSearchFragment.setUserVisibleHint(false);
   1014 
   1015             final FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1016             transaction.hide(mSearchFragment);
   1017             transaction.commitAllowingStateLoss();
   1018         }
   1019 
   1020         // We want to hide SearchView and show Tabs. Also focus on previously selected one.
   1021         actionBar.setDisplayShowCustomEnabled(false);
   1022         actionBar.setDisplayShowHomeEnabled(false);
   1023         actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
   1024 
   1025         sendFragmentVisibilityChange(mViewPager.getCurrentItem(), true);
   1026 
   1027         // Before exiting the search screen, reset swipe state.
   1028         mDuringSwipe = false;
   1029         mUserTabClick = false;
   1030 
   1031         mViewPager.setVisibility(View.VISIBLE);
   1032 
   1033         hideInputMethod(getCurrentFocus());
   1034 
   1035         // Request to update option menu.
   1036         invalidateOptionsMenu();
   1037 
   1038         // See comments in onActionViewExpanded()
   1039         mSearchView.onActionViewCollapsed();
   1040         mInSearchUi = false;
   1041     }
   1042 
   1043     private Fragment getFragmentAt(int position) {
   1044         switch (position) {
   1045             case TAB_INDEX_DIALER:
   1046                 return mDialpadFragment;
   1047             case TAB_INDEX_CALL_LOG:
   1048                 return mCallLogFragment;
   1049             case TAB_INDEX_FAVORITES:
   1050                 return mPhoneFavoriteFragment;
   1051             default:
   1052                 throw new IllegalStateException("Unknown fragment index: " + position);
   1053         }
   1054     }
   1055 
   1056     private void sendFragmentVisibilityChange(int position, boolean visibility) {
   1057         final Fragment fragment = getFragmentAt(position);
   1058         if (fragment instanceof ViewPagerVisibilityListener) {
   1059             ((ViewPagerVisibilityListener) fragment).onVisibilityChanged(visibility);
   1060         }
   1061     }
   1062 
   1063     /** Returns an Intent to launch Call Settings screen */
   1064     public static Intent getCallSettingsIntent() {
   1065         final Intent intent = new Intent(Intent.ACTION_MAIN);
   1066         intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME);
   1067         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
   1068         return intent;
   1069     }
   1070 
   1071     @Override
   1072     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   1073         if (resultCode != Activity.RESULT_OK) {
   1074             return;
   1075         }
   1076         switch (requestCode) {
   1077             case SUBACTIVITY_ACCOUNT_FILTER: {
   1078                 AccountFilterUtil.handleAccountFilterResult(
   1079                         mContactListFilterController, resultCode, data);
   1080             }
   1081             break;
   1082         }
   1083     }
   1084 }
   1085