Home | History | Annotate | Download | only in dialer
      1 /*
      2  * Copyright (C) 2013 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.dialer;
     18 
     19 import android.app.Fragment;
     20 import android.app.FragmentTransaction;
     21 import android.content.ActivityNotFoundException;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.ResolveInfo;
     26 import android.content.res.Configuration;
     27 import android.content.res.Resources;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.os.Trace;
     31 import android.provider.CallLog.Calls;
     32 import android.speech.RecognizerIntent;
     33 import android.support.design.widget.CoordinatorLayout;
     34 import android.support.v4.view.ViewPager;
     35 import android.support.v7.app.ActionBar;
     36 import android.telecom.PhoneAccount;
     37 import android.text.Editable;
     38 import android.text.TextUtils;
     39 import android.text.TextWatcher;
     40 import android.util.Log;
     41 import android.view.DragEvent;
     42 import android.view.Gravity;
     43 import android.view.KeyEvent;
     44 import android.view.Menu;
     45 import android.view.MenuItem;
     46 import android.view.MotionEvent;
     47 import android.view.View;
     48 import android.view.View.OnDragListener;
     49 import android.view.ViewTreeObserver;
     50 import android.view.animation.Animation;
     51 import android.view.animation.AnimationUtils;
     52 import android.widget.AbsListView.OnScrollListener;
     53 import android.widget.EditText;
     54 import android.widget.ImageButton;
     55 import android.widget.PopupMenu;
     56 import android.widget.TextView;
     57 import android.widget.Toast;
     58 
     59 import com.android.contacts.common.dialog.ClearFrequentsDialog;
     60 import com.android.contacts.common.interactions.ImportExportDialogFragment;
     61 import com.android.contacts.common.interactions.TouchPointManager;
     62 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
     63 import com.android.contacts.common.util.PermissionsUtil;
     64 import com.android.contacts.common.widget.FloatingActionButtonController;
     65 import com.android.dialer.calllog.CallLogActivity;
     66 import com.android.dialer.calllog.CallLogFragment;
     67 import com.android.dialer.database.DialerDatabaseHelper;
     68 import com.android.dialer.dialpad.DialpadFragment;
     69 import com.android.dialer.dialpad.SmartDialNameMatcher;
     70 import com.android.dialer.dialpad.SmartDialPrefix;
     71 import com.android.dialer.interactions.PhoneNumberInteraction;
     72 import com.android.dialer.list.DragDropController;
     73 import com.android.dialer.list.ListsFragment;
     74 import com.android.dialer.list.OnDragDropListener;
     75 import com.android.dialer.list.OnListFragmentScrolledListener;
     76 import com.android.dialer.list.PhoneFavoriteSquareTileView;
     77 import com.android.dialer.list.RegularSearchFragment;
     78 import com.android.dialer.list.SearchFragment;
     79 import com.android.dialer.list.SmartDialSearchFragment;
     80 import com.android.dialer.list.SpeedDialFragment;
     81 import com.android.dialer.logging.Logger;
     82 import com.android.dialer.logging.ScreenEvent;
     83 import com.android.dialer.settings.DialerSettingsActivity;
     84 import com.android.dialer.util.Assert;
     85 import com.android.dialer.util.DialerUtils;
     86 import com.android.dialer.util.IntentUtil;
     87 import com.android.dialer.util.IntentUtil.CallIntentBuilder;
     88 import com.android.dialer.util.TelecomUtil;
     89 import com.android.dialer.voicemail.VoicemailArchiveActivity;
     90 import com.android.dialer.widget.ActionBarController;
     91 import com.android.dialer.widget.SearchEditTextLayout;
     92 import com.android.dialerbind.DatabaseHelperManager;
     93 import com.android.dialerbind.ObjectFactory;
     94 import com.android.phone.common.animation.AnimUtils;
     95 import com.android.phone.common.animation.AnimationListenerAdapter;
     96 import com.google.common.annotations.VisibleForTesting;
     97 
     98 import java.util.ArrayList;
     99 import java.util.List;
    100 
    101 /**
    102  * The dialer tab's title is 'phone', a more common name (see strings.xml).
    103  */
    104 public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener,
    105         DialpadFragment.OnDialpadQueryChangedListener,
    106         OnListFragmentScrolledListener,
    107         CallLogFragment.HostInterface,
    108         DialpadFragment.HostInterface,
    109         ListsFragment.HostInterface,
    110         SpeedDialFragment.HostInterface,
    111         SearchFragment.HostInterface,
    112         OnDragDropListener,
    113         OnPhoneNumberPickerActionListener,
    114         PopupMenu.OnMenuItemClickListener,
    115         ViewPager.OnPageChangeListener,
    116         ActionBarController.ActivityUi {
    117     private static final String TAG = "DialtactsActivity";
    118 
    119     public static final boolean DEBUG = false;
    120 
    121     public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences";
    122 
    123     private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui";
    124     private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui";
    125     private static final String KEY_SEARCH_QUERY = "search_query";
    126     private static final String KEY_FIRST_LAUNCH = "first_launch";
    127     private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown";
    128 
    129     @VisibleForTesting
    130     public static final String TAG_DIALPAD_FRAGMENT = "dialpad";
    131     private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search";
    132     private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial";
    133     private static final String TAG_FAVORITES_FRAGMENT = "favorites";
    134 
    135     /**
    136      * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
    137      */
    138     private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
    139     public static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB";
    140 
    141     private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1;
    142 
    143     private static final int FAB_SCALE_IN_DELAY_MS = 300;
    144 
    145     private CoordinatorLayout mParentLayout;
    146 
    147     /**
    148      * Fragment containing the dialpad that slides into view
    149      */
    150     protected DialpadFragment mDialpadFragment;
    151 
    152     /**
    153      * Fragment for searching phone numbers using the alphanumeric keyboard.
    154      */
    155     private RegularSearchFragment mRegularSearchFragment;
    156 
    157     /**
    158      * Fragment for searching phone numbers using the dialpad.
    159      */
    160     private SmartDialSearchFragment mSmartDialSearchFragment;
    161 
    162     /**
    163      * Animation that slides in.
    164      */
    165     private Animation mSlideIn;
    166 
    167     /**
    168      * Animation that slides out.
    169      */
    170     private Animation mSlideOut;
    171 
    172     AnimationListenerAdapter mSlideInListener = new AnimationListenerAdapter() {
    173         @Override
    174         public void onAnimationEnd(Animation animation) {
    175             maybeEnterSearchUi();
    176         }
    177     };
    178 
    179     /**
    180      * Listener for after slide out animation completes on dialer fragment.
    181      */
    182     AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
    183         @Override
    184         public void onAnimationEnd(Animation animation) {
    185             commitDialpadFragmentHide();
    186         }
    187     };
    188 
    189     /**
    190      * Fragment containing the speed dial list, call history list, and all contacts list.
    191      */
    192     private ListsFragment mListsFragment;
    193 
    194     /**
    195      * Tracks whether onSaveInstanceState has been called. If true, no fragment transactions can
    196      * be commited.
    197      */
    198     private boolean mStateSaved;
    199     private boolean mIsRestarting;
    200     private boolean mInDialpadSearch;
    201     private boolean mInRegularSearch;
    202     private boolean mClearSearchOnPause;
    203     private boolean mIsDialpadShown;
    204     private boolean mShowDialpadOnResume;
    205 
    206     /**
    207      * Whether or not the device is in landscape orientation.
    208      */
    209     private boolean mIsLandscape;
    210 
    211     /**
    212      * True if the dialpad is only temporarily showing due to being in call
    213      */
    214     private boolean mInCallDialpadUp;
    215 
    216     /**
    217      * True when this activity has been launched for the first time.
    218      */
    219     private boolean mFirstLaunch;
    220 
    221     /**
    222      * Search query to be applied to the SearchView in the ActionBar once
    223      * onCreateOptionsMenu has been called.
    224      */
    225     private String mPendingSearchViewQuery;
    226 
    227     private PopupMenu mOverflowMenu;
    228     private EditText mSearchView;
    229     private View mVoiceSearchButton;
    230 
    231     private String mSearchQuery;
    232     private String mDialpadQuery;
    233 
    234     private DialerDatabaseHelper mDialerDatabaseHelper;
    235     private DragDropController mDragDropController;
    236     private ActionBarController mActionBarController;
    237 
    238     private FloatingActionButtonController mFloatingActionButtonController;
    239 
    240     private int mActionBarHeight;
    241     private int mPreviouslySelectedTabIndex;
    242 
    243     /**
    244      * The text returned from a voice search query.  Set in {@link #onActivityResult} and used in
    245      * {@link #onResume()} to populate the search box.
    246      */
    247     private String mVoiceSearchQuery;
    248 
    249     protected class OptionsPopupMenu extends PopupMenu {
    250         public OptionsPopupMenu(Context context, View anchor) {
    251             super(context, anchor, Gravity.END);
    252         }
    253 
    254         @Override
    255         public void show() {
    256             final boolean hasContactsPermission =
    257                     PermissionsUtil.hasContactsPermissions(DialtactsActivity.this);
    258             final Menu menu = getMenu();
    259             final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
    260             clearFrequents.setVisible(mListsFragment != null &&
    261                     mListsFragment.getSpeedDialFragment() != null &&
    262                     mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission);
    263 
    264             menu.findItem(R.id.menu_import_export).setVisible(hasContactsPermission);
    265             menu.findItem(R.id.menu_add_contact).setVisible(hasContactsPermission);
    266 
    267             menu.findItem(R.id.menu_history).setVisible(
    268                     PermissionsUtil.hasPhonePermissions(DialtactsActivity.this));
    269             super.show();
    270         }
    271     }
    272 
    273     /**
    274      * Listener that listens to drag events and sends their x and y coordinates to a
    275      * {@link DragDropController}.
    276      */
    277     private class LayoutOnDragListener implements OnDragListener {
    278         @Override
    279         public boolean onDrag(View v, DragEvent event) {
    280             if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
    281                 mDragDropController.handleDragHovered(v, (int) event.getX(), (int) event.getY());
    282             }
    283             return true;
    284         }
    285     }
    286 
    287     /**
    288      * Listener used to send search queries to the phone search fragment.
    289      */
    290     private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {
    291         @Override
    292         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    293         }
    294 
    295         @Override
    296         public void onTextChanged(CharSequence s, int start, int before, int count) {
    297             final String newText = s.toString();
    298             if (newText.equals(mSearchQuery)) {
    299                 // If the query hasn't changed (perhaps due to activity being destroyed
    300                 // and restored, or user launching the same DIAL intent twice), then there is
    301                 // no need to do anything here.
    302                 return;
    303             }
    304             if (DEBUG) {
    305                 Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText);
    306                 Log.d(TAG, "Previous Query: " + mSearchQuery);
    307             }
    308             mSearchQuery = newText;
    309 
    310             // Show search fragment only when the query string is changed to non-empty text.
    311             if (!TextUtils.isEmpty(newText)) {
    312                 // Call enterSearchUi only if we are switching search modes, or showing a search
    313                 // fragment for the first time.
    314                 final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||
    315                         (!mIsDialpadShown && mInRegularSearch);
    316                 if (!sameSearchMode) {
    317                     enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);
    318                 }
    319             }
    320 
    321             if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
    322                 mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
    323             } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
    324                 mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
    325             }
    326         }
    327 
    328         @Override
    329         public void afterTextChanged(Editable s) {
    330         }
    331     };
    332 
    333 
    334     /**
    335      * Open the search UI when the user clicks on the search box.
    336      */
    337     private final View.OnClickListener mSearchViewOnClickListener = new View.OnClickListener() {
    338         @Override
    339         public void onClick(View v) {
    340             if (!isInSearchUi()) {
    341                 mActionBarController.onSearchBoxTapped();
    342                 enterSearchUi(false /* smartDialSearch */, mSearchView.getText().toString(),
    343                         true /* animate */);
    344             }
    345         }
    346     };
    347 
    348     /**
    349      * Handles the user closing the soft keyboard.
    350      */
    351     private final View.OnKeyListener mSearchEditTextLayoutListener = new View.OnKeyListener() {
    352         @Override
    353         public boolean onKey(View v, int keyCode, KeyEvent event) {
    354             if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
    355                 if (TextUtils.isEmpty(mSearchView.getText().toString())) {
    356                     // If the search term is empty, close the search UI.
    357                     maybeExitSearchUi();
    358                 } else {
    359                     // If the search term is not empty, show the dialpad fab.
    360                     showFabInSearchUi();
    361                 }
    362             }
    363             return false;
    364         }
    365     };
    366 
    367     @Override
    368     public boolean dispatchTouchEvent(MotionEvent ev) {
    369         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    370             TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
    371         }
    372         return super.dispatchTouchEvent(ev);
    373     }
    374 
    375     @Override
    376     protected void onCreate(Bundle savedInstanceState) {
    377         Trace.beginSection(TAG + " onCreate");
    378         super.onCreate(savedInstanceState);
    379 
    380         mFirstLaunch = true;
    381 
    382         final Resources resources = getResources();
    383         mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height_large);
    384 
    385         Trace.beginSection(TAG + " setContentView");
    386         setContentView(R.layout.dialtacts_activity);
    387         Trace.endSection();
    388         getWindow().setBackgroundDrawable(null);
    389 
    390         Trace.beginSection(TAG + " setup Views");
    391         final ActionBar actionBar = getSupportActionBar();
    392         actionBar.setCustomView(R.layout.search_edittext);
    393         actionBar.setDisplayShowCustomEnabled(true);
    394         actionBar.setBackgroundDrawable(null);
    395 
    396         SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar
    397                 .getCustomView().findViewById(R.id.search_view_container);
    398         searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener);
    399 
    400         mActionBarController = new ActionBarController(this, searchEditTextLayout);
    401 
    402         mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view);
    403         mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
    404         mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button);
    405         searchEditTextLayout.findViewById(R.id.search_magnifying_glass)
    406                 .setOnClickListener(mSearchViewOnClickListener);
    407         searchEditTextLayout.findViewById(R.id.search_box_start_search)
    408                 .setOnClickListener(mSearchViewOnClickListener);
    409         searchEditTextLayout.setOnClickListener(mSearchViewOnClickListener);
    410         searchEditTextLayout.setCallback(new SearchEditTextLayout.Callback() {
    411             @Override
    412             public void onBackButtonClicked() {
    413                 onBackPressed();
    414             }
    415 
    416             @Override
    417             public void onSearchViewClicked() {
    418                 // Hide FAB, as the keyboard is shown.
    419                 mFloatingActionButtonController.scaleOut();
    420             }
    421         });
    422 
    423         mIsLandscape = getResources().getConfiguration().orientation
    424                 == Configuration.ORIENTATION_LANDSCAPE;
    425         mPreviouslySelectedTabIndex = ListsFragment.TAB_INDEX_SPEED_DIAL;
    426         final View floatingActionButtonContainer = findViewById(
    427                 R.id.floating_action_button_container);
    428         ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
    429         floatingActionButton.setOnClickListener(this);
    430         mFloatingActionButtonController = new FloatingActionButtonController(this,
    431                 floatingActionButtonContainer, floatingActionButton);
    432 
    433         ImageButton optionsMenuButton =
    434                 (ImageButton) searchEditTextLayout.findViewById(R.id.dialtacts_options_menu_button);
    435         optionsMenuButton.setOnClickListener(this);
    436         mOverflowMenu = buildOptionsMenu(searchEditTextLayout);
    437         optionsMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener());
    438 
    439         // Add the favorites fragment but only if savedInstanceState is null. Otherwise the
    440         // fragment manager is responsible for recreating it.
    441         if (savedInstanceState == null) {
    442             getFragmentManager().beginTransaction()
    443                     .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
    444                     .commit();
    445         } else {
    446             mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
    447             mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI);
    448             mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI);
    449             mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH);
    450             mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN);
    451             mActionBarController.restoreInstanceState(savedInstanceState);
    452         }
    453 
    454         final boolean isLayoutRtl = DialerUtils.isRtl();
    455         if (mIsLandscape) {
    456             mSlideIn = AnimationUtils.loadAnimation(this,
    457                     isLayoutRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
    458             mSlideOut = AnimationUtils.loadAnimation(this,
    459                     isLayoutRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
    460         } else {
    461             mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
    462             mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
    463         }
    464 
    465         mSlideIn.setInterpolator(AnimUtils.EASE_IN);
    466         mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
    467 
    468         mSlideIn.setAnimationListener(mSlideInListener);
    469         mSlideOut.setAnimationListener(mSlideOutListener);
    470 
    471         mParentLayout = (CoordinatorLayout) findViewById(R.id.dialtacts_mainlayout);
    472         mParentLayout.setOnDragListener(new LayoutOnDragListener());
    473         floatingActionButtonContainer.getViewTreeObserver().addOnGlobalLayoutListener(
    474                 new ViewTreeObserver.OnGlobalLayoutListener() {
    475                     @Override
    476                     public void onGlobalLayout() {
    477                         final ViewTreeObserver observer =
    478                                 floatingActionButtonContainer.getViewTreeObserver();
    479                         if (!observer.isAlive()) {
    480                             return;
    481                         }
    482                         observer.removeOnGlobalLayoutListener(this);
    483                         int screenWidth = mParentLayout.getWidth();
    484                         mFloatingActionButtonController.setScreenWidth(screenWidth);
    485                         mFloatingActionButtonController.align(
    486                                 getFabAlignment(), false /* animate */);
    487                     }
    488                 });
    489 
    490         Trace.endSection();
    491 
    492         Trace.beginSection(TAG + " initialize smart dialing");
    493         mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this);
    494         SmartDialPrefix.initializeNanpSettings(this);
    495         Trace.endSection();
    496         Trace.endSection();
    497     }
    498 
    499     @Override
    500     protected void onResume() {
    501         Trace.beginSection(TAG + " onResume");
    502         super.onResume();
    503 
    504         mStateSaved = false;
    505         if (mFirstLaunch) {
    506             displayFragment(getIntent());
    507         } else if (!phoneIsInUse() && mInCallDialpadUp) {
    508             hideDialpadFragment(false, true);
    509             mInCallDialpadUp = false;
    510         } else if (mShowDialpadOnResume) {
    511             showDialpadFragment(false);
    512             mShowDialpadOnResume = false;
    513         }
    514 
    515         // If there was a voice query result returned in the {@link #onActivityResult} callback, it
    516         // will have been stashed in mVoiceSearchQuery since the search results fragment cannot be
    517         // shown until onResume has completed.  Active the search UI and set the search term now.
    518         if (!TextUtils.isEmpty(mVoiceSearchQuery)) {
    519             mActionBarController.onSearchBoxTapped();
    520             mSearchView.setText(mVoiceSearchQuery);
    521             mVoiceSearchQuery = null;
    522         }
    523 
    524         mFirstLaunch = false;
    525 
    526         if (mIsRestarting) {
    527             // This is only called when the activity goes from resumed -> paused -> resumed, so it
    528             // will not cause an extra view to be sent out on rotation
    529             if (mIsDialpadShown) {
    530                 Logger.logScreenView(ScreenEvent.DIALPAD, this);
    531             }
    532             mIsRestarting = false;
    533         }
    534 
    535         prepareVoiceSearchButton();
    536         mDialerDatabaseHelper.startSmartDialUpdateThread();
    537         mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
    538 
    539         if (Calls.CONTENT_TYPE.equals(getIntent().getType())) {
    540             // Externally specified extras take precedence to EXTRA_SHOW_TAB, which is only
    541             // used internally.
    542             final Bundle extras = getIntent().getExtras();
    543             if (extras != null
    544                     && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) {
    545                 mListsFragment.showTab(ListsFragment.TAB_INDEX_VOICEMAIL);
    546             } else {
    547                 mListsFragment.showTab(ListsFragment.TAB_INDEX_HISTORY);
    548             }
    549         } else if (getIntent().hasExtra(EXTRA_SHOW_TAB)) {
    550             int index = getIntent().getIntExtra(EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_SPEED_DIAL);
    551             if (index < mListsFragment.getTabCount()) {
    552                 mListsFragment.showTab(index);
    553             }
    554         }
    555 
    556         setSearchBoxHint();
    557 
    558         Trace.endSection();
    559     }
    560 
    561     @Override
    562     protected void onRestart() {
    563         super.onRestart();
    564         mIsRestarting = true;
    565     }
    566 
    567     @Override
    568     protected void onPause() {
    569         // Only clear missed calls if the pause was not triggered by an orientation change
    570         // (or any other confirguration change)
    571         if (!isChangingConfigurations()) {
    572             updateMissedCalls();
    573         }
    574         if (mClearSearchOnPause) {
    575             hideDialpadAndSearchUi();
    576             mClearSearchOnPause = false;
    577         }
    578         if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) {
    579             commitDialpadFragmentHide();
    580         }
    581         super.onPause();
    582     }
    583 
    584     @Override
    585     protected void onSaveInstanceState(Bundle outState) {
    586         super.onSaveInstanceState(outState);
    587         outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
    588         outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
    589         outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
    590         outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
    591         outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown);
    592         mActionBarController.saveInstanceState(outState);
    593         mStateSaved = true;
    594     }
    595 
    596     @Override
    597     public void onAttachFragment(Fragment fragment) {
    598         if (fragment instanceof DialpadFragment) {
    599             mDialpadFragment = (DialpadFragment) fragment;
    600             if (!mIsDialpadShown && !mShowDialpadOnResume) {
    601                 final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    602                 transaction.hide(mDialpadFragment);
    603                 transaction.commit();
    604             }
    605         } else if (fragment instanceof SmartDialSearchFragment) {
    606             mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
    607             mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this);
    608             if (!TextUtils.isEmpty(mDialpadQuery)) {
    609                 mSmartDialSearchFragment.setAddToContactNumber(mDialpadQuery);
    610             }
    611         } else if (fragment instanceof SearchFragment) {
    612             mRegularSearchFragment = (RegularSearchFragment) fragment;
    613             mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this);
    614         } else if (fragment instanceof ListsFragment) {
    615             mListsFragment = (ListsFragment) fragment;
    616             mListsFragment.addOnPageChangeListener(this);
    617         }
    618     }
    619 
    620     protected void handleMenuSettings() {
    621         final Intent intent = new Intent(this, DialerSettingsActivity.class);
    622         startActivity(intent);
    623     }
    624 
    625     @Override
    626     public void onClick(View view) {
    627         int resId = view.getId();
    628         if (resId == R.id.floating_action_button) {
    629             if (mListsFragment.getCurrentTabIndex()
    630                     == ListsFragment.TAB_INDEX_ALL_CONTACTS && !mInRegularSearch) {
    631                 DialerUtils.startActivityWithErrorToast(
    632                         this,
    633                         IntentUtil.getNewContactIntent(),
    634                         R.string.add_contact_not_available);
    635             } else if (!mIsDialpadShown) {
    636                 mInCallDialpadUp = false;
    637                 showDialpadFragment(true);
    638             }
    639         } else if (resId == R.id.voice_search_button) {
    640             try {
    641                 startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
    642                         ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
    643             } catch (ActivityNotFoundException e) {
    644                 Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available,
    645                         Toast.LENGTH_SHORT).show();
    646             }
    647         } else if (resId == R.id.dialtacts_options_menu_button) {
    648             mOverflowMenu.show();
    649         } else {
    650             Log.wtf(TAG, "Unexpected onClick event from " + view);
    651         }
    652     }
    653 
    654     @Override
    655     public boolean onMenuItemClick(MenuItem item) {
    656         if (!isSafeToCommitTransactions()) {
    657             return true;
    658         }
    659 
    660         int resId = item.getItemId();
    661         if (resId == R.id.menu_history) {// Use explicit CallLogActivity intent instead of ACTION_VIEW +
    662             // CONTENT_TYPE, so that we always open our call log from our dialer
    663             final Intent intent = new Intent(this, CallLogActivity.class);
    664             startActivity(intent);
    665         } else if (resId == R.id.menu_add_contact) {
    666             DialerUtils.startActivityWithErrorToast(
    667                     this,
    668                     IntentUtil.getNewContactIntent(),
    669                     R.string.add_contact_not_available);
    670         } else if (resId == R.id.menu_import_export) {// We hard-code the "contactsAreAvailable" argument because doing it properly would
    671             // involve querying a {@link ProviderStatusLoader}, which we don't want to do right
    672             // now in Dialtacts for (potential) performance reasons. Compare with how it is
    673             // done in {@link PeopleActivity}.
    674             if (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) {
    675                 ImportExportDialogFragment.show(getFragmentManager(), true,
    676                         DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_FAVORITES);
    677             } else {
    678                 ImportExportDialogFragment.show(getFragmentManager(), true,
    679                         DialtactsActivity.class, ImportExportDialogFragment.EXPORT_MODE_DEFAULT);
    680             }
    681             Logger.logScreenView(ScreenEvent.IMPORT_EXPORT_CONTACTS, this);
    682             return true;
    683         } else if (resId == R.id.menu_clear_frequents) {
    684             ClearFrequentsDialog.show(getFragmentManager());
    685             Logger.logScreenView(ScreenEvent.CLEAR_FREQUENTS, this);
    686             return true;
    687         } else if (resId == R.id.menu_call_settings) {
    688             handleMenuSettings();
    689             Logger.logScreenView(ScreenEvent.SETTINGS, this);
    690             return true;
    691         } else if (resId == R.id.menu_archive) {
    692             final Intent intent = new Intent(this, VoicemailArchiveActivity.class);
    693             startActivity(intent);
    694             return true;
    695         }
    696         return false;
    697     }
    698 
    699     @Override
    700     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    701         if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) {
    702             if (resultCode == RESULT_OK) {
    703                 final ArrayList<String> matches = data.getStringArrayListExtra(
    704                         RecognizerIntent.EXTRA_RESULTS);
    705                 if (matches.size() > 0) {
    706                     final String match = matches.get(0);
    707                     mVoiceSearchQuery = match;
    708                 } else {
    709                     Log.e(TAG, "Voice search - nothing heard");
    710                 }
    711             } else {
    712                 Log.e(TAG, "Voice search failed");
    713             }
    714         }
    715         super.onActivityResult(requestCode, resultCode, data);
    716     }
    717 
    718     /**
    719      * Update the number of unread voicemails (potentially other tabs) displayed next to the tab
    720      * icon.
    721      */
    722     public void updateTabUnreadCounts() {
    723         mListsFragment.updateTabUnreadCounts();
    724     }
    725 
    726     /**
    727      * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual
    728      * updates are handled by a callback which is invoked after the dialpad fragment is shown.
    729      * @see #onDialpadShown
    730      */
    731     private void showDialpadFragment(boolean animate) {
    732         if (mIsDialpadShown || mStateSaved) {
    733             return;
    734         }
    735         mIsDialpadShown = true;
    736 
    737         mListsFragment.setUserVisibleHint(false);
    738 
    739         final FragmentTransaction ft = getFragmentManager().beginTransaction();
    740         if (mDialpadFragment == null) {
    741             mDialpadFragment = new DialpadFragment();
    742             ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT);
    743         } else {
    744             ft.show(mDialpadFragment);
    745         }
    746 
    747         mDialpadFragment.setAnimate(animate);
    748         Logger.logScreenView(ScreenEvent.DIALPAD, this);
    749         ft.commit();
    750 
    751         if (animate) {
    752             mFloatingActionButtonController.scaleOut();
    753         } else {
    754             mFloatingActionButtonController.setVisible(false);
    755             maybeEnterSearchUi();
    756         }
    757         mActionBarController.onDialpadUp();
    758 
    759         mListsFragment.getView().animate().alpha(0).withLayer();
    760 
    761         //adjust the title, so the user will know where we're at when the activity start/resumes.
    762         setTitle(R.string.launcherDialpadActivityLabel);
    763     }
    764 
    765     /**
    766      * Callback from child DialpadFragment when the dialpad is shown.
    767      */
    768     public void onDialpadShown() {
    769         Assert.assertNotNull(mDialpadFragment);
    770         if (mDialpadFragment.getAnimate()) {
    771             mDialpadFragment.getView().startAnimation(mSlideIn);
    772         } else {
    773             mDialpadFragment.setYFraction(0);
    774         }
    775 
    776         updateSearchFragmentPosition();
    777     }
    778 
    779     /**
    780      * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in
    781      * a callback after the hide animation ends.
    782      * @see #commitDialpadFragmentHide
    783      */
    784     public void hideDialpadFragment(boolean animate, boolean clearDialpad) {
    785         if (mDialpadFragment == null || mDialpadFragment.getView() == null) {
    786             return;
    787         }
    788         if (clearDialpad) {
    789             // Temporarily disable accessibility when we clear the dialpad, since it should be
    790             // invisible and should not announce anything.
    791             mDialpadFragment.getDigitsWidget().setImportantForAccessibility(
    792                     View.IMPORTANT_FOR_ACCESSIBILITY_NO);
    793             mDialpadFragment.clearDialpad();
    794             mDialpadFragment.getDigitsWidget().setImportantForAccessibility(
    795                     View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
    796         }
    797         if (!mIsDialpadShown) {
    798             return;
    799         }
    800         mIsDialpadShown = false;
    801         mDialpadFragment.setAnimate(animate);
    802         mListsFragment.setUserVisibleHint(true);
    803         mListsFragment.sendScreenViewForCurrentPosition();
    804 
    805         updateSearchFragmentPosition();
    806 
    807         mFloatingActionButtonController.align(getFabAlignment(), animate);
    808         if (animate) {
    809             mDialpadFragment.getView().startAnimation(mSlideOut);
    810         } else {
    811             commitDialpadFragmentHide();
    812         }
    813 
    814         mActionBarController.onDialpadDown();
    815 
    816         if (isInSearchUi()) {
    817             if (TextUtils.isEmpty(mSearchQuery)) {
    818                 exitSearchUi();
    819             }
    820         }
    821         //reset the title to normal.
    822         setTitle(R.string.launcherActivityLabel);
    823     }
    824 
    825     /**
    826      * Finishes hiding the dialpad fragment after any animations are completed.
    827      */
    828     private void commitDialpadFragmentHide() {
    829         if (!mStateSaved && mDialpadFragment != null && !mDialpadFragment.isHidden()) {
    830             final FragmentTransaction ft = getFragmentManager().beginTransaction();
    831             ft.hide(mDialpadFragment);
    832             ft.commit();
    833         }
    834         mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
    835     }
    836 
    837     private void updateSearchFragmentPosition() {
    838         SearchFragment fragment = null;
    839         if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
    840             fragment = mSmartDialSearchFragment;
    841         } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
    842             fragment = mRegularSearchFragment;
    843         }
    844         if (fragment != null && fragment.isVisible()) {
    845             fragment.updatePosition(true /* animate */);
    846         }
    847     }
    848 
    849     @Override
    850     public boolean isInSearchUi() {
    851         return mInDialpadSearch || mInRegularSearch;
    852     }
    853 
    854     @Override
    855     public boolean hasSearchQuery() {
    856         return !TextUtils.isEmpty(mSearchQuery);
    857     }
    858 
    859     @Override
    860     public boolean shouldShowActionBar() {
    861         return mListsFragment.shouldShowActionBar();
    862     }
    863 
    864     private void setNotInSearchUi() {
    865         mInDialpadSearch = false;
    866         mInRegularSearch = false;
    867     }
    868 
    869     private void hideDialpadAndSearchUi() {
    870         if (mIsDialpadShown) {
    871             hideDialpadFragment(false, true);
    872         } else {
    873             exitSearchUi();
    874         }
    875     }
    876 
    877     private void prepareVoiceSearchButton() {
    878         final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    879         if (canIntentBeHandled(voiceIntent)) {
    880             mVoiceSearchButton.setVisibility(View.VISIBLE);
    881             mVoiceSearchButton.setOnClickListener(this);
    882         } else {
    883             mVoiceSearchButton.setVisibility(View.GONE);
    884         }
    885     }
    886 
    887     public boolean isNearbyPlacesSearchEnabled() {
    888         return false;
    889     }
    890 
    891     protected int getSearchBoxHint () {
    892         return R.string.dialer_hint_find_contact;
    893     }
    894 
    895     /**
    896      * Sets the hint text for the contacts search box
    897      */
    898     private void setSearchBoxHint() {
    899         SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) getSupportActionBar()
    900                 .getCustomView().findViewById(R.id.search_view_container);
    901         ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search))
    902                 .setHint(getSearchBoxHint());
    903     }
    904 
    905     protected OptionsPopupMenu buildOptionsMenu(View invoker) {
    906         final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker);
    907         popupMenu.inflate(R.menu.dialtacts_options);
    908         if (ObjectFactory.isVoicemailArchiveEnabled(this)) {
    909             popupMenu.getMenu().findItem(R.id.menu_archive).setVisible(true);
    910         }
    911         popupMenu.setOnMenuItemClickListener(this);
    912         return popupMenu;
    913     }
    914 
    915     @Override
    916     public boolean onCreateOptionsMenu(Menu menu) {
    917         if (mPendingSearchViewQuery != null) {
    918             mSearchView.setText(mPendingSearchViewQuery);
    919             mPendingSearchViewQuery = null;
    920         }
    921         if (mActionBarController != null) {
    922             mActionBarController.restoreActionBarOffset();
    923         }
    924         return false;
    925     }
    926 
    927     /**
    928      * Returns true if the intent is due to hitting the green send key (hardware call button:
    929      * KEYCODE_CALL) while in a call.
    930      *
    931      * @param intent the intent that launched this activity
    932      * @return true if the intent is due to hitting the green send key while in a call
    933      */
    934     private boolean isSendKeyWhileInCall(Intent intent) {
    935         // If there is a call in progress and the user launched the dialer by hitting the call
    936         // button, go straight to the in-call screen.
    937         final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction());
    938 
    939         if (callKey) {
    940             TelecomUtil.showInCallScreen(this, false);
    941             return true;
    942         }
    943 
    944         return false;
    945     }
    946 
    947     /**
    948      * Sets the current tab based on the intent's request type
    949      *
    950      * @param intent Intent that contains information about which tab should be selected
    951      */
    952     private void displayFragment(Intent intent) {
    953         // If we got here by hitting send and we're in call forward along to the in-call activity
    954         if (isSendKeyWhileInCall(intent)) {
    955             finish();
    956             return;
    957         }
    958 
    959         final boolean showDialpadChooser = phoneIsInUse() && !DialpadFragment.isAddCallMode(intent);
    960         if (showDialpadChooser || (intent.getData() != null && isDialIntent(intent))) {
    961             showDialpadFragment(false);
    962             mDialpadFragment.setStartedFromNewIntent(true);
    963             if (showDialpadChooser && !mDialpadFragment.isVisible()) {
    964                 mInCallDialpadUp = true;
    965             }
    966         }
    967     }
    968 
    969     @Override
    970     public void onNewIntent(Intent newIntent) {
    971         setIntent(newIntent);
    972 
    973         mStateSaved = false;
    974         displayFragment(newIntent);
    975 
    976         invalidateOptionsMenu();
    977     }
    978 
    979     /** Returns true if the given intent contains a phone number to populate the dialer with */
    980     private boolean isDialIntent(Intent intent) {
    981         final String action = intent.getAction();
    982         if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
    983             return true;
    984         }
    985         if (Intent.ACTION_VIEW.equals(action)) {
    986             final Uri data = intent.getData();
    987             if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) {
    988                 return true;
    989             }
    990         }
    991         return false;
    992     }
    993 
    994     /**
    995      * Shows the search fragment
    996      */
    997     private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) {
    998         if (mStateSaved || getFragmentManager().isDestroyed()) {
    999             // Weird race condition where fragment is doing work after the activity is destroyed
   1000             // due to talkback being on (b/10209937). Just return since we can't do any
   1001             // constructive here.
   1002             return;
   1003         }
   1004 
   1005         if (DEBUG) {
   1006             Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch);
   1007         }
   1008 
   1009         final FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1010         if (mInDialpadSearch && mSmartDialSearchFragment != null) {
   1011             transaction.remove(mSmartDialSearchFragment);
   1012         } else if (mInRegularSearch && mRegularSearchFragment != null) {
   1013             transaction.remove(mRegularSearchFragment);
   1014         }
   1015 
   1016         final String tag;
   1017         if (smartDialSearch) {
   1018             tag = TAG_SMARTDIAL_SEARCH_FRAGMENT;
   1019         } else {
   1020             tag = TAG_REGULAR_SEARCH_FRAGMENT;
   1021         }
   1022         mInDialpadSearch = smartDialSearch;
   1023         mInRegularSearch = !smartDialSearch;
   1024 
   1025         mFloatingActionButtonController.scaleOut();
   1026 
   1027         SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
   1028         if (animate) {
   1029             transaction.setCustomAnimations(android.R.animator.fade_in, 0);
   1030         } else {
   1031             transaction.setTransition(FragmentTransaction.TRANSIT_NONE);
   1032         }
   1033         if (fragment == null) {
   1034             if (smartDialSearch) {
   1035                 fragment = new SmartDialSearchFragment();
   1036             } else {
   1037                 fragment = ObjectFactory.newRegularSearchFragment();
   1038                 fragment.setOnTouchListener(new View.OnTouchListener() {
   1039                     @Override
   1040                     public boolean onTouch(View v, MotionEvent event) {
   1041                         // Show the FAB when the user touches the lists fragment and the soft
   1042                         // keyboard is hidden.
   1043                         hideDialpadFragment(true, false);
   1044                         showFabInSearchUi();
   1045                         return false;
   1046                     }
   1047                 });
   1048             }
   1049             transaction.add(R.id.dialtacts_frame, fragment, tag);
   1050         } else {
   1051             transaction.show(fragment);
   1052         }
   1053         // DialtactsActivity will provide the options menu
   1054         fragment.setHasOptionsMenu(false);
   1055         fragment.setShowEmptyListForNullQuery(true);
   1056         if (!smartDialSearch) {
   1057             fragment.setQueryString(query, false /* delaySelection */);
   1058         }
   1059         transaction.commit();
   1060 
   1061         if (animate) {
   1062             mListsFragment.getView().animate().alpha(0).withLayer();
   1063         }
   1064         mListsFragment.setUserVisibleHint(false);
   1065 
   1066         if (smartDialSearch) {
   1067             Logger.logScreenView(ScreenEvent.SMART_DIAL_SEARCH, this);
   1068         } else {
   1069             Logger.logScreenView(ScreenEvent.REGULAR_SEARCH, this);
   1070         }
   1071     }
   1072 
   1073     /**
   1074      * Hides the search fragment
   1075      */
   1076     private void exitSearchUi() {
   1077         // See related bug in enterSearchUI();
   1078         if (getFragmentManager().isDestroyed() || mStateSaved) {
   1079             return;
   1080         }
   1081 
   1082         mSearchView.setText(null);
   1083 
   1084         if (mDialpadFragment != null) {
   1085             mDialpadFragment.clearDialpad();
   1086         }
   1087 
   1088         setNotInSearchUi();
   1089 
   1090         // Restore the FAB for the lists fragment.
   1091         if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) {
   1092             mFloatingActionButtonController.setVisible(false);
   1093         }
   1094         mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
   1095         onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */);
   1096         onPageSelected(mListsFragment.getCurrentTabIndex());
   1097 
   1098         final FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1099         if (mSmartDialSearchFragment != null) {
   1100             transaction.remove(mSmartDialSearchFragment);
   1101         }
   1102         if (mRegularSearchFragment != null) {
   1103             transaction.remove(mRegularSearchFragment);
   1104         }
   1105         transaction.commit();
   1106 
   1107         mListsFragment.getView().animate().alpha(1).withLayer();
   1108 
   1109         if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
   1110             // If the dialpad fragment wasn't previously visible, then send a screen view because
   1111             // we are exiting regular search. Otherwise, the screen view will be sent by
   1112             // {@link #hideDialpadFragment}.
   1113             mListsFragment.sendScreenViewForCurrentPosition();
   1114             mListsFragment.setUserVisibleHint(true);
   1115         }
   1116 
   1117         mActionBarController.onSearchUiExited();
   1118     }
   1119 
   1120     @Override
   1121     public void onBackPressed() {
   1122         if (mStateSaved) {
   1123             return;
   1124         }
   1125         if (mIsDialpadShown) {
   1126             if (TextUtils.isEmpty(mSearchQuery) ||
   1127                     (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()
   1128                             && mSmartDialSearchFragment.getAdapter().getCount() == 0)) {
   1129                 exitSearchUi();
   1130             }
   1131             hideDialpadFragment(true, false);
   1132         } else if (isInSearchUi()) {
   1133             exitSearchUi();
   1134             DialerUtils.hideInputMethod(mParentLayout);
   1135         } else {
   1136             super.onBackPressed();
   1137         }
   1138     }
   1139 
   1140     private void maybeEnterSearchUi() {
   1141         if (!isInSearchUi()) {
   1142             enterSearchUi(true /* isSmartDial */, mSearchQuery, false);
   1143         }
   1144     }
   1145 
   1146     /**
   1147      * @return True if the search UI was exited, false otherwise
   1148      */
   1149     private boolean maybeExitSearchUi() {
   1150         if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) {
   1151             exitSearchUi();
   1152             DialerUtils.hideInputMethod(mParentLayout);
   1153             return true;
   1154         }
   1155         return false;
   1156     }
   1157 
   1158     private void showFabInSearchUi() {
   1159         mFloatingActionButtonController.changeIcon(
   1160                 getResources().getDrawable(R.drawable.fab_ic_dial),
   1161                 getResources().getString(R.string.action_menu_dialpad_button));
   1162         mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
   1163         mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
   1164     }
   1165 
   1166     @Override
   1167     public void onDialpadQueryChanged(String query) {
   1168         mDialpadQuery = query;
   1169         if (mSmartDialSearchFragment != null) {
   1170             mSmartDialSearchFragment.setAddToContactNumber(query);
   1171         }
   1172         final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
   1173                 SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
   1174 
   1175         if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
   1176             if (DEBUG) {
   1177                 Log.d(TAG, "onDialpadQueryChanged - new query: " + query);
   1178             }
   1179             if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
   1180                 // This callback can happen if the dialpad fragment is recreated because of
   1181                 // activity destruction. In that case, don't update the search view because
   1182                 // that would bring the user back to the search fragment regardless of the
   1183                 // previous state of the application. Instead, just return here and let the
   1184                 // fragment manager correctly figure out whatever fragment was last displayed.
   1185                 if (!TextUtils.isEmpty(normalizedQuery)) {
   1186                     mPendingSearchViewQuery = normalizedQuery;
   1187                 }
   1188                 return;
   1189             }
   1190             mSearchView.setText(normalizedQuery);
   1191         }
   1192 
   1193         try {
   1194             if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
   1195                 mDialpadFragment.process_quote_emergency_unquote(normalizedQuery);
   1196             }
   1197         } catch (Exception ignored) {
   1198             // Skip any exceptions for this piece of code
   1199         }
   1200     }
   1201 
   1202     @Override
   1203     public boolean onDialpadSpacerTouchWithEmptyQuery() {
   1204         if (mInDialpadSearch && mSmartDialSearchFragment != null
   1205                 && !mSmartDialSearchFragment.isShowingPermissionRequest()) {
   1206             hideDialpadFragment(true /* animate */, true /* clearDialpad */);
   1207             return true;
   1208         }
   1209         return false;
   1210     }
   1211 
   1212     @Override
   1213     public void onListFragmentScrollStateChange(int scrollState) {
   1214         if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
   1215             hideDialpadFragment(true, false);
   1216             DialerUtils.hideInputMethod(mParentLayout);
   1217         }
   1218     }
   1219 
   1220     @Override
   1221     public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount,
   1222                                      int totalItemCount) {
   1223         // TODO: No-op for now. This should eventually show/hide the actionBar based on
   1224         // interactions with the ListsFragments.
   1225     }
   1226 
   1227     private boolean phoneIsInUse() {
   1228         return TelecomUtil.isInCall(this);
   1229     }
   1230 
   1231     private boolean canIntentBeHandled(Intent intent) {
   1232         final PackageManager packageManager = getPackageManager();
   1233         final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
   1234                 PackageManager.MATCH_DEFAULT_ONLY);
   1235         return resolveInfo != null && resolveInfo.size() > 0;
   1236     }
   1237 
   1238     /**
   1239      * Called when the user has long-pressed a contact tile to start a drag operation.
   1240      */
   1241     @Override
   1242     public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
   1243         mListsFragment.showRemoveView(true);
   1244     }
   1245 
   1246     @Override
   1247     public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {
   1248     }
   1249 
   1250     /**
   1251      * Called when the user has released a contact tile after long-pressing it.
   1252      */
   1253     @Override
   1254     public void onDragFinished(int x, int y) {
   1255         mListsFragment.showRemoveView(false);
   1256     }
   1257 
   1258     @Override
   1259     public void onDroppedOnRemove() {}
   1260 
   1261     /**
   1262      * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer
   1263      * once it has been attached to the activity.
   1264      */
   1265     @Override
   1266     public void setDragDropController(DragDropController dragController) {
   1267         mDragDropController = dragController;
   1268         mListsFragment.getRemoveView().setDragDropController(dragController);
   1269     }
   1270 
   1271     /**
   1272      * Implemented to satisfy {@link SpeedDialFragment.HostInterface}
   1273      */
   1274     @Override
   1275     public void showAllContactsTab() {
   1276         if (mListsFragment != null) {
   1277             mListsFragment.showTab(ListsFragment.TAB_INDEX_ALL_CONTACTS);
   1278         }
   1279     }
   1280 
   1281     /**
   1282      * Implemented to satisfy {@link CallLogFragment.HostInterface}
   1283      */
   1284     @Override
   1285     public void showDialpad() {
   1286         showDialpadFragment(true);
   1287     }
   1288 
   1289     @Override
   1290     public void onPickDataUri(Uri dataUri, boolean isVideoCall, int callInitiationType) {
   1291         mClearSearchOnPause = true;
   1292         PhoneNumberInteraction.startInteractionForPhoneCall(
   1293                 DialtactsActivity.this, dataUri, isVideoCall, callInitiationType);
   1294     }
   1295 
   1296     @Override
   1297     public void onPickPhoneNumber(String phoneNumber, boolean isVideoCall, int callInitiationType) {
   1298         if (phoneNumber == null) {
   1299             // Invalid phone number, but let the call go through so that InCallUI can show
   1300             // an error message.
   1301             phoneNumber = "";
   1302         }
   1303 
   1304         final Intent intent = new CallIntentBuilder(phoneNumber)
   1305                 .setIsVideoCall(isVideoCall)
   1306                 .setCallInitiationType(callInitiationType)
   1307                 .build();
   1308 
   1309         DialerUtils.startActivityWithErrorToast(this, intent);
   1310         mClearSearchOnPause = true;
   1311     }
   1312 
   1313     @Override
   1314     public void onShortcutIntentCreated(Intent intent) {
   1315         Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
   1316     }
   1317 
   1318     @Override
   1319     public void onHomeInActionBarSelected() {
   1320         exitSearchUi();
   1321     }
   1322 
   1323     @Override
   1324     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
   1325         int tabIndex = mListsFragment.getCurrentTabIndex();
   1326 
   1327         // Scroll the button from center to end when moving from the Speed Dial to Call History tab.
   1328         // In RTL, scroll when the current tab is Call History instead, since the order of the tabs
   1329         // is reversed and the ViewPager returns the left tab position during scroll.
   1330         boolean isRtl = DialerUtils.isRtl();
   1331         if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) {
   1332             mFloatingActionButtonController.onPageScrolled(positionOffset);
   1333         } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_HISTORY && !mIsLandscape) {
   1334             mFloatingActionButtonController.onPageScrolled(1 - positionOffset);
   1335         } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) {
   1336             mFloatingActionButtonController.onPageScrolled(1);
   1337         }
   1338     }
   1339 
   1340     @Override
   1341     public void onPageSelected(int position) {
   1342         updateMissedCalls();
   1343         int tabIndex = mListsFragment.getCurrentTabIndex();
   1344         mPreviouslySelectedTabIndex = tabIndex;
   1345         if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
   1346             mFloatingActionButtonController.changeIcon(
   1347                     getResources().getDrawable(R.drawable.ic_person_add_24dp),
   1348                     getResources().getString(R.string.search_shortcut_create_new_contact));
   1349         } else {
   1350             mFloatingActionButtonController.changeIcon(
   1351                     getResources().getDrawable(R.drawable.fab_ic_dial),
   1352                     getResources().getString(R.string.action_menu_dialpad_button));
   1353         }
   1354     }
   1355 
   1356     @Override
   1357     public void onPageScrollStateChanged(int state) {
   1358     }
   1359 
   1360     @Override
   1361     public boolean isActionBarShowing() {
   1362         return mActionBarController.isActionBarShowing();
   1363     }
   1364 
   1365     @Override
   1366     public ActionBarController getActionBarController() {
   1367         return mActionBarController;
   1368     }
   1369 
   1370     @Override
   1371     public boolean isDialpadShown() {
   1372         return mIsDialpadShown;
   1373     }
   1374 
   1375     @Override
   1376     public int getDialpadHeight() {
   1377         if (mDialpadFragment != null) {
   1378             return mDialpadFragment.getDialpadHeight();
   1379         }
   1380         return 0;
   1381     }
   1382 
   1383     @Override
   1384     public int getActionBarHideOffset() {
   1385         return getSupportActionBar().getHideOffset();
   1386     }
   1387 
   1388     @Override
   1389     public void setActionBarHideOffset(int offset) {
   1390         getSupportActionBar().setHideOffset(offset);
   1391     }
   1392 
   1393     @Override
   1394     public int getActionBarHeight() {
   1395         return mActionBarHeight;
   1396     }
   1397 
   1398     private int getFabAlignment() {
   1399         if (!mIsLandscape && !isInSearchUi() &&
   1400                 mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) {
   1401             return FloatingActionButtonController.ALIGN_MIDDLE;
   1402         }
   1403         return FloatingActionButtonController.ALIGN_END;
   1404     }
   1405 
   1406     private void updateMissedCalls() {
   1407         if (mPreviouslySelectedTabIndex == ListsFragment.TAB_INDEX_HISTORY) {
   1408             mListsFragment.markMissedCallsAsReadAndRemoveNotifications();
   1409         }
   1410     }
   1411 }
   1412