1 /* 2 * Copyright (C) 2017 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.main.impl; 18 19 import android.app.Fragment; 20 import android.app.FragmentTransaction; 21 import android.content.ActivityNotFoundException; 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.speech.RecognizerIntent; 25 import android.support.annotation.Nullable; 26 import android.support.annotation.VisibleForTesting; 27 import android.support.design.widget.FloatingActionButton; 28 import android.support.v7.app.AppCompatActivity; 29 import android.text.TextUtils; 30 import android.view.MenuItem; 31 import android.view.View; 32 import android.view.animation.Animation; 33 import android.view.animation.Animation.AnimationListener; 34 import android.widget.Toast; 35 import com.android.contacts.common.dialog.ClearFrequentsDialog; 36 import com.android.dialer.app.calllog.CallLogActivity; 37 import com.android.dialer.app.settings.DialerSettingsActivity; 38 import com.android.dialer.callintent.CallInitiationType; 39 import com.android.dialer.common.LogUtil; 40 import com.android.dialer.constants.ActivityRequestCodes; 41 import com.android.dialer.dialpadview.DialpadFragment; 42 import com.android.dialer.dialpadview.DialpadFragment.DialpadListener; 43 import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener; 44 import com.android.dialer.logging.DialerImpression; 45 import com.android.dialer.logging.Logger; 46 import com.android.dialer.logging.ScreenEvent; 47 import com.android.dialer.main.impl.bottomnav.BottomNavBar; 48 import com.android.dialer.main.impl.toolbar.MainToolbar; 49 import com.android.dialer.main.impl.toolbar.SearchBarListener; 50 import com.android.dialer.searchfragment.list.NewSearchFragment; 51 import com.android.dialer.searchfragment.list.NewSearchFragment.SearchFragmentListener; 52 import com.android.dialer.smartdial.util.SmartDialNameMatcher; 53 import com.android.dialer.util.TransactionSafeActivity; 54 import com.google.common.base.Optional; 55 import java.util.ArrayList; 56 import java.util.List; 57 58 /** 59 * Search controller for handling all the logic related to entering and exiting the search UI. 60 * 61 * <p>Components modified are: 62 * 63 * <ul> 64 * <li>Bottom Nav Bar, completely hidden when in search ui. 65 * <li>FAB, visible in dialpad search when dialpad is hidden. Otherwise, FAB is hidden. 66 * <li>Toolbar, expanded and visible when dialpad is hidden. Otherwise, hidden off screen. 67 * <li>Dialpad, shown through fab clicks and hidden with Android back button. 68 * </ul> 69 * 70 * @see #onBackPressed() 71 */ 72 public class MainSearchController implements SearchBarListener { 73 74 private static final String KEY_IS_FAB_HIDDEN = "is_fab_hidden"; 75 private static final String KEY_TOOLBAR_SHADOW_VISIBILITY = "toolbar_shadow_visibility"; 76 private static final String KEY_IS_TOOLBAR_EXPANDED = "is_toolbar_expanded"; 77 private static final String KEY_IS_TOOLBAR_SLIDE_UP = "is_toolbar_slide_up"; 78 79 private static final String DIALPAD_FRAGMENT_TAG = "dialpad_fragment_tag"; 80 private static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; 81 82 private final TransactionSafeActivity activity; 83 private final BottomNavBar bottomNav; 84 private final FloatingActionButton fab; 85 private final MainToolbar toolbar; 86 private final View toolbarShadow; 87 88 /** View located underneath the toolbar that needs to animate with it. */ 89 private final View fragmentContainer; 90 91 private final List<OnSearchShowListener> onSearchShowListenerList = new ArrayList<>(); 92 93 /** 94 * True when an action happens that closes search (like leaving the app or placing a call). We 95 * want to wait until onPause is called otherwise the transition will look extremely janky. 96 */ 97 private boolean closeSearchOnPause; 98 99 private boolean callPlacedFromSearch; 100 private boolean requestingPermission; 101 102 private DialpadFragment dialpadFragment; 103 private NewSearchFragment searchFragment; 104 MainSearchController( TransactionSafeActivity activity, BottomNavBar bottomNav, FloatingActionButton fab, MainToolbar toolbar, View toolbarShadow, View fragmentContainer)105 public MainSearchController( 106 TransactionSafeActivity activity, 107 BottomNavBar bottomNav, 108 FloatingActionButton fab, 109 MainToolbar toolbar, 110 View toolbarShadow, 111 View fragmentContainer) { 112 this.activity = activity; 113 this.bottomNav = bottomNav; 114 this.fab = fab; 115 this.toolbar = toolbar; 116 this.toolbarShadow = toolbarShadow; 117 this.fragmentContainer = fragmentContainer; 118 119 dialpadFragment = 120 (DialpadFragment) activity.getFragmentManager().findFragmentByTag(DIALPAD_FRAGMENT_TAG); 121 searchFragment = 122 (NewSearchFragment) activity.getFragmentManager().findFragmentByTag(SEARCH_FRAGMENT_TAG); 123 } 124 125 /** Should be called if we're showing the dialpad because of a new ACTION_DIAL intent. */ showDialpadFromNewIntent()126 public void showDialpadFromNewIntent() { 127 LogUtil.enterBlock("MainSearchController.showDialpadFromNewIntent"); 128 if (isDialpadVisible()) { 129 // One scenario where this can happen is if the user has the dialpad open when the receive a 130 // call and press add call in the in call ui which calls this method. 131 LogUtil.i("MainSearchController.showDialpadFromNewIntent", "Dialpad is already visible."); 132 133 // Mark started from new intent in case there is a phone number in the intent 134 dialpadFragment.setStartedFromNewIntent(true); 135 return; 136 } 137 showDialpad(/* animate=*/ false, /* fromNewIntent=*/ true); 138 } 139 140 /** Shows the dialpad, hides the FAB and slides the toolbar off screen. */ showDialpad(boolean animate)141 public void showDialpad(boolean animate) { 142 LogUtil.enterBlock("MainSearchController.showDialpad"); 143 showDialpad(animate, false); 144 } 145 showDialpad(boolean animate, boolean fromNewIntent)146 private void showDialpad(boolean animate, boolean fromNewIntent) { 147 if (isDialpadVisible()) { 148 LogUtil.e("MainSearchController.showDialpad", "Dialpad is already visible."); 149 return; 150 } 151 152 Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_DIALPAD, activity); 153 154 fab.hide(); 155 toolbar.slideUp(animate, fragmentContainer); 156 toolbar.expand(animate, Optional.absent(), /* requestFocus */ false); 157 toolbarShadow.setVisibility(View.VISIBLE); 158 159 activity.setTitle(R.string.dialpad_activity_title); 160 161 FragmentTransaction transaction = activity.getFragmentManager().beginTransaction(); 162 163 // Show Search 164 if (searchFragment == null) { 165 searchFragment = NewSearchFragment.newInstance(); 166 transaction.add(R.id.search_fragment_container, searchFragment, SEARCH_FRAGMENT_TAG); 167 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 168 } else if (!isSearchVisible()) { 169 transaction.show(searchFragment); 170 } 171 172 // Show Dialpad 173 if (dialpadFragment == null) { 174 dialpadFragment = new DialpadFragment(); 175 dialpadFragment.setStartedFromNewIntent(fromNewIntent); 176 transaction.add(R.id.dialpad_fragment_container, dialpadFragment, DIALPAD_FRAGMENT_TAG); 177 searchFragment.setQuery("", CallInitiationType.Type.DIALPAD); 178 } else { 179 dialpadFragment.setStartedFromNewIntent(fromNewIntent); 180 transaction.show(dialpadFragment); 181 } 182 transaction.commit(); 183 184 notifyListenersOnSearchOpen(); 185 } 186 187 /** 188 * Hides the dialpad, reveals the FAB and slides the toolbar back onto the screen. 189 * 190 * <p>This method intentionally "hides" and does not "remove" the dialpad in order to preserve its 191 * state (i.e. we call {@link FragmentTransaction#hide(Fragment)} instead of {@link 192 * FragmentTransaction#remove(Fragment)}. 193 * 194 * @see {@link #closeSearch(boolean)} to "remove" the dialpad. 195 */ hideDialpad(boolean animate)196 private void hideDialpad(boolean animate) { 197 LogUtil.enterBlock("MainSearchController.hideDialpad"); 198 if (dialpadFragment == null) { 199 LogUtil.e("MainSearchController.hideDialpad", "Dialpad fragment is null."); 200 return; 201 } 202 203 if (!dialpadFragment.isAdded()) { 204 LogUtil.e("MainSearchController.hideDialpad", "Dialpad fragment is not added."); 205 return; 206 } 207 208 if (dialpadFragment.isHidden()) { 209 LogUtil.e("MainSearchController.hideDialpad", "Dialpad fragment is already hidden."); 210 return; 211 } 212 213 if (!dialpadFragment.isDialpadSlideUp()) { 214 LogUtil.e("MainSearchController.hideDialpad", "Dialpad fragment is already slide down."); 215 return; 216 } 217 218 fab.show(); 219 toolbar.slideDown(animate, fragmentContainer); 220 toolbar.transferQueryFromDialpad(dialpadFragment.getQuery()); 221 activity.setTitle(R.string.main_activity_label); 222 223 dialpadFragment.setAnimate(animate); 224 dialpadFragment.slideDown( 225 animate, 226 new AnimationListener() { 227 @Override 228 public void onAnimationStart(Animation animation) {} 229 230 @Override 231 public void onAnimationEnd(Animation animation) { 232 if (activity.isSafeToCommitTransactions() 233 && !(activity.isFinishing() || activity.isDestroyed())) { 234 activity.getFragmentManager().beginTransaction().hide(dialpadFragment).commit(); 235 } 236 } 237 238 @Override 239 public void onAnimationRepeat(Animation animation) {} 240 }); 241 } 242 hideBottomNav()243 private void hideBottomNav() { 244 bottomNav.setVisibility(View.GONE); 245 } 246 showBottomNav()247 private void showBottomNav() { 248 bottomNav.setVisibility(View.VISIBLE); 249 } 250 251 /** Should be called when {@link DialpadListener#onDialpadShown()} is called. */ onDialpadShown()252 public void onDialpadShown() { 253 LogUtil.enterBlock("MainSearchController.onDialpadShown"); 254 dialpadFragment.slideUp(true); 255 hideBottomNav(); 256 } 257 258 /** 259 * @see SearchFragmentListener#onSearchListTouch() 260 * <p>There are 4 scenarios we support to provide a nice UX experience: 261 * <ol> 262 * <li>When the dialpad is visible with an empty query, close the search UI. 263 * <li>When the dialpad is visible with a non-empty query, hide the dialpad. 264 * <li>When the regular search UI is visible with an empty query, close the search UI. 265 * <li>When the regular search UI is visible with a non-empty query, hide the keyboard. 266 * </ol> 267 */ onSearchListTouch()268 public void onSearchListTouch() { 269 LogUtil.enterBlock("MainSearchController.onSearchListTouched"); 270 if (isDialpadVisible()) { 271 if (TextUtils.isEmpty(dialpadFragment.getQuery())) { 272 Logger.get(activity) 273 .logImpression( 274 DialerImpression.Type.MAIN_TOUCH_DIALPAD_SEARCH_LIST_TO_CLOSE_SEARCH_AND_DIALPAD); 275 closeSearch(true); 276 } else { 277 Logger.get(activity) 278 .logImpression(DialerImpression.Type.MAIN_TOUCH_DIALPAD_SEARCH_LIST_TO_HIDE_DIALPAD); 279 hideDialpad(/* animate=*/ true); 280 } 281 } else if (isSearchVisible()) { 282 if (TextUtils.isEmpty(toolbar.getQuery())) { 283 Logger.get(activity) 284 .logImpression(DialerImpression.Type.MAIN_TOUCH_SEARCH_LIST_TO_CLOSE_SEARCH); 285 closeSearch(true); 286 } else { 287 Logger.get(activity) 288 .logImpression(DialerImpression.Type.MAIN_TOUCH_SEARCH_LIST_TO_HIDE_KEYBOARD); 289 closeKeyboard(); 290 } 291 } 292 } 293 294 /** 295 * Should be called when the user presses the back button. 296 * 297 * @return true if #onBackPressed() handled to action. 298 */ onBackPressed()299 public boolean onBackPressed() { 300 if (isDialpadVisible() && !TextUtils.isEmpty(dialpadFragment.getQuery())) { 301 LogUtil.i("MainSearchController.onBackPressed", "Dialpad visible with query"); 302 Logger.get(activity) 303 .logImpression(DialerImpression.Type.MAIN_PRESS_BACK_BUTTON_TO_HIDE_DIALPAD); 304 hideDialpad(/* animate=*/ true); 305 return true; 306 } else if (isSearchVisible()) { 307 LogUtil.i("MainSearchController.onBackPressed", "Search is visible"); 308 Logger.get(activity) 309 .logImpression( 310 isDialpadVisible() 311 ? DialerImpression.Type.MAIN_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH_AND_DIALPAD 312 : DialerImpression.Type.MAIN_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH); 313 closeSearch(true); 314 return true; 315 } else { 316 return false; 317 } 318 } 319 320 /** Calls {@link #hideDialpad(boolean)}, removes the search fragment and clears the dialpad. */ closeSearch(boolean animate)321 private void closeSearch(boolean animate) { 322 LogUtil.enterBlock("MainSearchController.closeSearch"); 323 if (searchFragment == null) { 324 LogUtil.e("MainSearchController.closeSearch", "Search fragment is null."); 325 return; 326 } 327 328 if (!searchFragment.isAdded()) { 329 LogUtil.e("MainSearchController.closeSearch", "Search fragment isn't added."); 330 return; 331 } 332 333 if (searchFragment.isHidden()) { 334 LogUtil.e("MainSearchController.closeSearch", "Search fragment is already hidden."); 335 return; 336 } 337 338 if (isDialpadVisible()) { 339 hideDialpad(animate); 340 } else if (!fab.isShown()) { 341 fab.show(); 342 } 343 showBottomNav(); 344 toolbar.collapse(animate); 345 toolbarShadow.setVisibility(View.GONE); 346 activity.getFragmentManager().beginTransaction().hide(searchFragment).commit(); 347 348 // Clear the dialpad so the phone number isn't persisted between search sessions. 349 if (dialpadFragment != null) { 350 // Temporarily disable accessibility when we clear the dialpad, since it should be 351 // invisible and should not announce anything. 352 dialpadFragment 353 .getDigitsWidget() 354 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 355 dialpadFragment.clearDialpad(); 356 dialpadFragment 357 .getDigitsWidget() 358 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); 359 } 360 361 notifyListenersOnSearchClose(); 362 } 363 364 @Nullable getDialpadFragment()365 protected DialpadFragment getDialpadFragment() { 366 return dialpadFragment; 367 } 368 isDialpadVisible()369 private boolean isDialpadVisible() { 370 return dialpadFragment != null 371 && dialpadFragment.isAdded() 372 && !dialpadFragment.isHidden() 373 && dialpadFragment.isDialpadSlideUp(); 374 } 375 isSearchVisible()376 private boolean isSearchVisible() { 377 return searchFragment != null && searchFragment.isAdded() && !searchFragment.isHidden(); 378 } 379 380 /** Returns true if the search UI is visible. */ isInSearch()381 public boolean isInSearch() { 382 return isSearchVisible(); 383 } 384 385 /** Closes the keyboard if necessary. */ closeKeyboard()386 private void closeKeyboard() { 387 if (searchFragment != null && searchFragment.isAdded()) { 388 toolbar.hideKeyboard(); 389 } 390 } 391 392 /** 393 * Opens search in regular/search bar search mode. 394 * 395 * <p>Hides fab, expands toolbar and starts the search fragment. 396 */ 397 @Override onSearchBarClicked()398 public void onSearchBarClicked() { 399 LogUtil.enterBlock("MainSearchController.onSearchBarClicked"); 400 Logger.get(activity).logImpression(DialerImpression.Type.MAIN_CLICK_SEARCH_BAR); 401 openSearch(Optional.absent()); 402 } 403 openSearch(Optional<String> query)404 private void openSearch(Optional<String> query) { 405 LogUtil.enterBlock("MainSearchController.openSearch"); 406 407 Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_SEARCH, activity); 408 409 fab.hide(); 410 toolbar.expand(/* animate=*/ true, query, /* requestFocus */ true); 411 toolbar.showKeyboard(); 412 toolbarShadow.setVisibility(View.VISIBLE); 413 hideBottomNav(); 414 415 FragmentTransaction transaction = activity.getFragmentManager().beginTransaction(); 416 // Show Search 417 if (searchFragment == null) { 418 searchFragment = NewSearchFragment.newInstance(); 419 transaction.add(R.id.search_fragment_container, searchFragment, SEARCH_FRAGMENT_TAG); 420 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 421 } else if (!isSearchVisible()) { 422 transaction.show(searchFragment); 423 } 424 425 searchFragment.setQuery( 426 query.isPresent() ? query.get() : "", CallInitiationType.Type.REGULAR_SEARCH); 427 428 if (activity.isSafeToCommitTransactions()) { 429 transaction.commit(); 430 } 431 432 notifyListenersOnSearchOpen(); 433 } 434 435 @Override onSearchBackButtonClicked()436 public void onSearchBackButtonClicked() { 437 LogUtil.enterBlock("MainSearchController.onSearchBackButtonClicked"); 438 closeSearch(true); 439 } 440 441 @Override onSearchQueryUpdated(String query)442 public void onSearchQueryUpdated(String query) { 443 if (searchFragment != null) { 444 searchFragment.setQuery(query, CallInitiationType.Type.REGULAR_SEARCH); 445 } 446 } 447 448 /** @see OnDialpadQueryChangedListener#onDialpadQueryChanged(java.lang.String) */ onDialpadQueryChanged(String query)449 public void onDialpadQueryChanged(String query) { 450 String normalizedQuery = SmartDialNameMatcher.normalizeNumber(/* context = */ activity, query); 451 if (searchFragment != null) { 452 searchFragment.setRawNumber(query); 453 searchFragment.setQuery(normalizedQuery, CallInitiationType.Type.DIALPAD); 454 } 455 dialpadFragment.process_quote_emergency_unquote(normalizedQuery); 456 } 457 458 @Override onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback)459 public void onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback) { 460 Logger.get(activity).logImpression(DialerImpression.Type.MAIN_CLICK_SEARCH_BAR_VOICE_BUTTON); 461 try { 462 Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 463 activity.startActivityForResult(voiceIntent, ActivityRequestCodes.DIALTACTS_VOICE_SEARCH); 464 } catch (ActivityNotFoundException e) { 465 Toast.makeText(activity, R.string.voice_search_not_available, Toast.LENGTH_SHORT).show(); 466 } 467 } 468 469 @Override onMenuItemClicked(MenuItem menuItem)470 public boolean onMenuItemClicked(MenuItem menuItem) { 471 if (menuItem.getItemId() == R.id.settings) { 472 activity.startActivity(new Intent(activity, DialerSettingsActivity.class)); 473 Logger.get(activity).logScreenView(ScreenEvent.Type.SETTINGS, activity); 474 return true; 475 } else if (menuItem.getItemId() == R.id.clear_frequents) { 476 ClearFrequentsDialog.show(activity.getFragmentManager()); 477 Logger.get(activity).logScreenView(ScreenEvent.Type.CLEAR_FREQUENTS, activity); 478 return true; 479 } else if (menuItem.getItemId() == R.id.menu_call_history) { 480 final Intent intent = new Intent(activity, CallLogActivity.class); 481 activity.startActivity(intent); 482 } 483 return false; 484 } 485 486 @Override onActivityPause()487 public void onActivityPause() { 488 LogUtil.enterBlock("MainSearchController.onActivityPause"); 489 closeKeyboard(); 490 491 if (closeSearchOnPause) { 492 if (isInSearch() && (callPlacedFromSearch || !isDialpadVisible())) { 493 closeSearch(false); 494 } 495 closeSearchOnPause = false; 496 callPlacedFromSearch = false; 497 } 498 } 499 500 @Override onUserLeaveHint()501 public void onUserLeaveHint() { 502 if (isInSearch()) { 503 // Requesting a permission causes this to be called and we want search to remain open when 504 // that happens. Otherwise, close search. 505 closeSearchOnPause = !requestingPermission; 506 507 // Always hide the keyboard when the user leaves dialer (including permission requests) 508 closeKeyboard(); 509 } 510 } 511 512 @Override onCallPlacedFromSearch()513 public void onCallPlacedFromSearch() { 514 closeSearchOnPause = true; 515 callPlacedFromSearch = true; 516 } 517 518 @Override requestingPermission()519 public void requestingPermission() { 520 LogUtil.enterBlock("MainSearchController.requestingPermission"); 521 requestingPermission = true; 522 } 523 onVoiceResults(int resultCode, Intent data)524 public void onVoiceResults(int resultCode, Intent data) { 525 if (resultCode == AppCompatActivity.RESULT_OK) { 526 ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); 527 if (matches.size() > 0) { 528 LogUtil.i("MainSearchController.onVoiceResults", "voice search - match found"); 529 openSearch(Optional.of(matches.get(0))); 530 } else { 531 LogUtil.i("MainSearchController.onVoiceResults", "voice search - nothing heard"); 532 } 533 } else { 534 LogUtil.e("MainSearchController.onVoiceResults", "voice search failed"); 535 } 536 } 537 onSaveInstanceState(Bundle bundle)538 public void onSaveInstanceState(Bundle bundle) { 539 bundle.putBoolean(KEY_IS_FAB_HIDDEN, !fab.isShown()); 540 bundle.putInt(KEY_TOOLBAR_SHADOW_VISIBILITY, toolbarShadow.getVisibility()); 541 bundle.putBoolean(KEY_IS_TOOLBAR_EXPANDED, toolbar.isExpanded()); 542 bundle.putBoolean(KEY_IS_TOOLBAR_SLIDE_UP, toolbar.isSlideUp()); 543 } 544 onRestoreInstanceState(Bundle savedInstanceState)545 public void onRestoreInstanceState(Bundle savedInstanceState) { 546 toolbarShadow.setVisibility(savedInstanceState.getInt(KEY_TOOLBAR_SHADOW_VISIBILITY)); 547 if (savedInstanceState.getBoolean(KEY_IS_FAB_HIDDEN, false)) { 548 fab.hide(); 549 } 550 boolean isSlideUp = savedInstanceState.getBoolean(KEY_IS_TOOLBAR_SLIDE_UP, false); 551 if (isSlideUp) { 552 toolbar.slideUp(false, fragmentContainer); 553 } 554 if (savedInstanceState.getBoolean(KEY_IS_TOOLBAR_EXPANDED, false)) { 555 // If the toolbar is slide up, that means the dialpad is showing. Thus we don't want to 556 // request focus or we'll break physical/bluetooth keyboards typing. 557 toolbar.expand(/* animate */ false, Optional.absent(), /* requestFocus */ !isSlideUp); 558 } 559 } 560 addOnSearchShowListener(OnSearchShowListener listener)561 public void addOnSearchShowListener(OnSearchShowListener listener) { 562 onSearchShowListenerList.add(listener); 563 } 564 removeOnSearchShowListener(OnSearchShowListener listener)565 public void removeOnSearchShowListener(OnSearchShowListener listener) { 566 onSearchShowListenerList.remove(listener); 567 } 568 notifyListenersOnSearchOpen()569 private void notifyListenersOnSearchOpen() { 570 for (OnSearchShowListener listener : onSearchShowListenerList) { 571 listener.onSearchOpen(); 572 } 573 } 574 notifyListenersOnSearchClose()575 private void notifyListenersOnSearchClose() { 576 for (OnSearchShowListener listener : onSearchShowListenerList) { 577 listener.onSearchClose(); 578 } 579 } 580 581 /** Listener for search fragment show states change */ 582 public interface OnSearchShowListener { onSearchOpen()583 void onSearchOpen(); 584 onSearchClose()585 void onSearchClose(); 586 } 587 588 @VisibleForTesting setDialpadFragment(DialpadFragment dialpadFragment)589 void setDialpadFragment(DialpadFragment dialpadFragment) { 590 this.dialpadFragment = dialpadFragment; 591 } 592 593 @VisibleForTesting setSearchFragment(NewSearchFragment searchFragment)594 void setSearchFragment(NewSearchFragment searchFragment) { 595 this.searchFragment = searchFragment; 596 } 597 } 598