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