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