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