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