1 /* 2 * Copyright (C) 2018 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.Activity; 20 import android.app.Fragment; 21 import android.app.FragmentManager; 22 import android.app.KeyguardManager; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.database.ContentObserver; 27 import android.database.Cursor; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.provider.CallLog.Calls; 32 import android.provider.ContactsContract.QuickContact; 33 import android.provider.VoicemailContract; 34 import android.support.annotation.Nullable; 35 import android.support.design.widget.FloatingActionButton; 36 import android.support.design.widget.Snackbar; 37 import android.support.v4.content.ContextCompat; 38 import android.support.v7.app.AppCompatActivity; 39 import android.support.v7.widget.Toolbar; 40 import android.telecom.PhoneAccount; 41 import android.telecom.PhoneAccountHandle; 42 import android.telephony.TelephonyManager; 43 import android.text.TextUtils; 44 import android.view.View; 45 import android.widget.ImageView; 46 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 47 import com.android.dialer.animation.AnimUtils; 48 import com.android.dialer.app.DialtactsActivity; 49 import com.android.dialer.app.calllog.CallLogAdapter; 50 import com.android.dialer.app.calllog.CallLogFragment; 51 import com.android.dialer.app.calllog.CallLogFragment.CallLogFragmentListener; 52 import com.android.dialer.app.calllog.CallLogNotificationsService; 53 import com.android.dialer.app.calllog.IntentProvider; 54 import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment; 55 import com.android.dialer.app.list.DragDropController; 56 import com.android.dialer.app.list.OldSpeedDialFragment; 57 import com.android.dialer.app.list.OnDragDropListener; 58 import com.android.dialer.app.list.OnListFragmentScrolledListener; 59 import com.android.dialer.app.list.PhoneFavoriteSquareTileView; 60 import com.android.dialer.app.list.RemoveView; 61 import com.android.dialer.callcomposer.CallComposerActivity; 62 import com.android.dialer.calldetails.CallDetailsActivity; 63 import com.android.dialer.callintent.CallIntentBuilder; 64 import com.android.dialer.callintent.CallSpecificAppData; 65 import com.android.dialer.common.FragmentUtils.FragmentUtilListener; 66 import com.android.dialer.common.LogUtil; 67 import com.android.dialer.common.concurrent.DialerExecutorComponent; 68 import com.android.dialer.common.concurrent.ThreadUtil; 69 import com.android.dialer.common.concurrent.UiListener; 70 import com.android.dialer.compat.CompatUtils; 71 import com.android.dialer.configprovider.ConfigProviderBindings; 72 import com.android.dialer.constants.ActivityRequestCodes; 73 import com.android.dialer.contactsfragment.ContactsFragment; 74 import com.android.dialer.contactsfragment.ContactsFragment.Header; 75 import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; 76 import com.android.dialer.database.CallLogQueryHandler; 77 import com.android.dialer.database.Database; 78 import com.android.dialer.dialpadview.DialpadFragment; 79 import com.android.dialer.dialpadview.DialpadFragment.DialpadListener; 80 import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback; 81 import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener; 82 import com.android.dialer.duo.DuoComponent; 83 import com.android.dialer.interactions.PhoneNumberInteraction; 84 import com.android.dialer.logging.DialerImpression; 85 import com.android.dialer.logging.Logger; 86 import com.android.dialer.main.MainActivityPeer; 87 import com.android.dialer.main.impl.bottomnav.BottomNavBar; 88 import com.android.dialer.main.impl.bottomnav.BottomNavBar.OnBottomNavTabSelectedListener; 89 import com.android.dialer.main.impl.bottomnav.BottomNavBar.TabIndex; 90 import com.android.dialer.main.impl.toolbar.MainToolbar; 91 import com.android.dialer.metrics.Metrics; 92 import com.android.dialer.metrics.MetricsComponent; 93 import com.android.dialer.postcall.PostCall; 94 import com.android.dialer.precall.PreCall; 95 import com.android.dialer.searchfragment.list.NewSearchFragment.SearchFragmentListener; 96 import com.android.dialer.smartdial.util.SmartDialPrefix; 97 import com.android.dialer.storage.StorageComponent; 98 import com.android.dialer.telecom.TelecomUtil; 99 import com.android.dialer.util.DialerUtils; 100 import com.android.dialer.util.PermissionsUtil; 101 import com.android.dialer.util.TransactionSafeActivity; 102 import com.android.dialer.voicemail.listui.error.VoicemailStatusCorruptionHandler; 103 import com.android.dialer.voicemail.listui.error.VoicemailStatusCorruptionHandler.Source; 104 import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker; 105 import com.android.dialer.voicemailstatus.VoicemailStatusHelper; 106 import com.android.voicemail.VoicemailComponent; 107 import com.google.common.util.concurrent.ListenableFuture; 108 import java.util.Locale; 109 import java.util.concurrent.TimeUnit; 110 111 /** 112 * OldMainActivityPeer which implements all of the old fragments we know and love <3 113 * 114 * <p>TODO(calderwoodra): Deprecate this class when we launch NewmainActivityPeer. 115 */ 116 public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListener { 117 118 private static final String KEY_SAVED_LANGUAGE_CODE = "saved_language_code"; 119 private static final String KEY_CURRENT_TAB = "current_tab"; 120 private static final String KEY_LAST_TAB = "last_tab"; 121 122 /** Action and extra to let the activity know which tab to open up to. */ 123 private static final String ACTION_SHOW_TAB = "ACTION_SHOW_TAB"; 124 125 private static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB"; 126 127 private final MainActivity mainActivity; 128 129 // Contacts 130 private MainOnContactSelectedListener onContactSelectedListener; 131 132 // Dialpad and Search 133 private MainDialpadFragmentHost dialpadFragmentHostInterface; 134 private MainSearchController searchController; 135 private MainOnDialpadQueryChangedListener onDialpadQueryChangedListener; 136 private MainDialpadListener dialpadListener; 137 private MainSearchFragmentListener searchFragmentListener; 138 139 // Action Mode 140 private MainCallLogAdapterOnActionModeStateChangedListener 141 callLogAdapterOnActionModeStateChangedListener; 142 143 // Call Log 144 private MainCallLogHost callLogHostInterface; 145 private MainCallLogFragmentListener callLogFragmentListener; 146 private MainOnListFragmentScrolledListener onListFragmentScrolledListener; 147 148 // Speed Dial 149 private MainOnPhoneNumberPickerActionListener onPhoneNumberPickerActionListener; 150 private MainOldSpeedDialFragmentHost oldSpeedDialFragmentHost; 151 152 /** Language the device was in last time {@link #onSaveInstanceState(Bundle)} was called. */ 153 private String savedLanguageCode; 154 155 private LastTabController lastTabController; 156 157 private BottomNavBar bottomNav; 158 private View snackbarContainer; 159 private UiListener<String> getLastOutgoingCallListener; 160 161 public static Intent getShowTabIntent(Context context, @TabIndex int tabIndex) { 162 Intent intent = new Intent(context, MainActivity.class); 163 intent.setAction(ACTION_SHOW_TAB); 164 intent.putExtra(EXTRA_SHOW_TAB, tabIndex); 165 // TODO(calderwoodra): Do we need to set some URI data here 166 return intent; 167 } 168 169 static boolean isShowTabIntent(Intent intent) { 170 return ACTION_SHOW_TAB.equals(intent.getAction()) && intent.hasExtra(EXTRA_SHOW_TAB); 171 } 172 173 static @TabIndex int getTabFromIntent(Intent intent) { 174 return intent.getIntExtra(EXTRA_SHOW_TAB, -1); 175 } 176 177 public OldMainActivityPeer(MainActivity mainActivity) { 178 this.mainActivity = mainActivity; 179 } 180 181 @Override 182 public void onActivityCreate(Bundle savedInstanceState) { 183 LogUtil.enterBlock("OldMainActivityPeer.onActivityCreate"); 184 mainActivity.setContentView(R.layout.main_activity); 185 initUiListeners(); 186 initLayout(savedInstanceState); 187 SmartDialPrefix.initializeNanpSettings(mainActivity); 188 } 189 190 private void initUiListeners() { 191 getLastOutgoingCallListener = 192 DialerExecutorComponent.get(mainActivity) 193 .createUiListener(mainActivity.getFragmentManager(), "Query last phone number"); 194 } 195 196 private void initLayout(Bundle savedInstanceState) { 197 onContactSelectedListener = new MainOnContactSelectedListener(mainActivity); 198 dialpadFragmentHostInterface = new MainDialpadFragmentHost(); 199 200 snackbarContainer = mainActivity.findViewById(R.id.coordinator_layout); 201 202 FloatingActionButton fab = mainActivity.findViewById(R.id.fab); 203 fab.setOnClickListener( 204 v -> { 205 Logger.get(mainActivity) 206 .logImpression(DialerImpression.Type.MAIN_CLICK_FAB_TO_OPEN_DIALPAD); 207 searchController.showDialpad(true); 208 }); 209 210 MainToolbar toolbar = mainActivity.findViewById(R.id.toolbar); 211 toolbar.maybeShowSimulator(mainActivity); 212 mainActivity.setSupportActionBar(mainActivity.findViewById(R.id.toolbar)); 213 214 bottomNav = mainActivity.findViewById(R.id.bottom_nav_bar); 215 MainBottomNavBarBottomNavTabListener bottomNavTabListener = 216 new MainBottomNavBarBottomNavTabListener( 217 mainActivity, mainActivity.getFragmentManager(), fab); 218 bottomNav.addOnTabSelectedListener(bottomNavTabListener); 219 // TODO(uabdullah): Handle case of when a sim is inserted/removed while the activity is open. 220 boolean showVoicemailTab = canVoicemailTabBeShown(mainActivity); 221 bottomNav.showVoicemail(showVoicemailTab); 222 223 callLogFragmentListener = 224 new MainCallLogFragmentListener( 225 mainActivity, mainActivity.getContentResolver(), bottomNav, toolbar); 226 bottomNav.addOnTabSelectedListener(callLogFragmentListener); 227 228 searchController = 229 getNewMainSearchController( 230 bottomNav, fab, toolbar, mainActivity.findViewById(R.id.toolbar_shadow)); 231 toolbar.setSearchBarListener(searchController); 232 233 onDialpadQueryChangedListener = getNewOnDialpadQueryChangedListener(searchController); 234 dialpadListener = 235 new MainDialpadListener(mainActivity, searchController, getLastOutgoingCallListener); 236 searchFragmentListener = new MainSearchFragmentListener(searchController); 237 callLogAdapterOnActionModeStateChangedListener = 238 new MainCallLogAdapterOnActionModeStateChangedListener(); 239 callLogHostInterface = new MainCallLogHost(searchController, fab); 240 241 onListFragmentScrolledListener = new MainOnListFragmentScrolledListener(snackbarContainer); 242 onPhoneNumberPickerActionListener = new MainOnPhoneNumberPickerActionListener(mainActivity); 243 oldSpeedDialFragmentHost = 244 new MainOldSpeedDialFragmentHost( 245 bottomNav, 246 mainActivity.findViewById(R.id.contact_tile_drag_shadow_overlay), 247 mainActivity.findViewById(R.id.remove_view), 248 mainActivity.findViewById(R.id.search_view_container), 249 toolbar); 250 251 lastTabController = new LastTabController(mainActivity, bottomNav, showVoicemailTab); 252 253 // Restore our view state if needed, else initialize as if the app opened for the first time 254 if (savedInstanceState != null) { 255 savedLanguageCode = savedInstanceState.getString(KEY_SAVED_LANGUAGE_CODE); 256 searchController.onRestoreInstanceState(savedInstanceState); 257 bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB)); 258 } else { 259 onHandleIntent(mainActivity.getIntent()); 260 } 261 } 262 263 /** 264 * Check and return whether the voicemail tab should be shown or not. This includes the following 265 * criteria under which we show the voicemail tab: 266 * <li>The voicemail number exists (e.g we are able to dial into listen to voicemail or press and 267 * hold 1) (TODO (uabdullah): Handle this case properly) 268 * <li>Visual voicemail is enabled from the settings tab 269 * <li>Visual voicemail carrier is supported by dialer 270 * <li>There is no voicemail carrier app installed. 271 * 272 * @param context 273 * @return return if voicemail tab should be shown or not depending on what the voicemail state is 274 * for the carrier. 275 */ 276 private static boolean canVoicemailTabBeShown(Context context) { 277 PhoneAccountHandle defaultUserSelectedAccount = 278 TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_VOICEMAIL); 279 280 if (!isVoicemailAvailable(context, defaultUserSelectedAccount)) { 281 LogUtil.i("OldMainActivityPeer.canVoicemailTabBeShown", "Voicemail is not available"); 282 return false; 283 } 284 285 if (VoicemailComponent.get(context) 286 .getVoicemailClient() 287 .isVoicemailEnabled(context, defaultUserSelectedAccount)) { 288 LogUtil.i("OldMainActivityPeer.canVoicemailTabBeShown", "Voicemail is enabled"); 289 return true; 290 } 291 LogUtil.i("OldMainActivityPeer.canVoicemailTabBeShown", "returning false"); 292 return false; 293 } 294 295 /** 296 * Check if voicemail is enabled/accessible. 297 * 298 * @return true if voicemail is enabled and accessible. Note that this can be false "temporarily" 299 * after the app boot e.g if the sim isn't fully recognized. TODO(uabdullah): Possibly add a 300 * listener of some kind to detect when a sim is recognized. TODO(uabdullah): Move this to a 301 * utility class or wrap it all in a static inner class. 302 */ 303 private static boolean isVoicemailAvailable( 304 Context context, PhoneAccountHandle defaultUserSelectedAccount) { 305 306 if (!TelecomUtil.hasReadPhoneStatePermission(context)) { 307 LogUtil.i( 308 "OldMainActivityPeer.isVoicemailAvailable", 309 "No read phone permisison or not the default dialer."); 310 return false; 311 } 312 313 if (defaultUserSelectedAccount == null) { 314 // In a single-SIM phone, there is no default outgoing phone account selected by 315 // the user, so just call TelephonyManager#getVoicemailNumber directly. 316 return !TextUtils.isEmpty(getTelephonyManager(context).getVoiceMailNumber()); 317 } else { 318 return !TextUtils.isEmpty( 319 TelecomUtil.getVoicemailNumber(context, defaultUserSelectedAccount)); 320 } 321 } 322 323 private static TelephonyManager getTelephonyManager(Context context) { 324 return context.getSystemService(TelephonyManager.class); 325 } 326 327 @Override 328 public void onNewIntent(Intent intent) { 329 LogUtil.enterBlock("OldMainActivityPeer.onNewIntent"); 330 onHandleIntent(intent); 331 } 332 333 private void onHandleIntent(Intent intent) { 334 // Some important implementation notes: 335 // 1) If the intent contains extra data to open to a specific screen (e.g. DIAL intent), when 336 // the user leaves that screen, they will return here and add see a blank screen unless we 337 // select a tab here. 338 // 2) Don't return early here in case the intent does contain extra data. 339 // 3) External intents should take priority over other intents (like Calls.CONTENT_TYPE). 340 if (Calls.CONTENT_TYPE.equals(intent.getType())) { 341 Bundle extras = intent.getExtras(); 342 if (extras != null && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) { 343 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Voicemail content type intent"); 344 bottomNav.selectTab(TabIndex.VOICEMAIL); 345 Logger.get(mainActivity).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CLICKED); 346 } else { 347 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Call log content type intent"); 348 bottomNav.selectTab(TabIndex.CALL_LOG); 349 } 350 351 } else if (isShowTabIntent(intent)) { 352 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Show tab intent"); 353 bottomNav.selectTab(getTabFromIntent(intent)); 354 } else if (lastTabController.isEnabled) { 355 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Show last tab"); 356 lastTabController.selectLastTab(); 357 } else { 358 bottomNav.selectTab(TabIndex.SPEED_DIAL); 359 } 360 361 if (isDialOrAddCallIntent(intent)) { 362 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Dial or add call intent"); 363 // Dialpad will grab the intent and populate the number 364 searchController.showDialpadFromNewIntent(); 365 } 366 367 if (intent.getBooleanExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, false)) { 368 LogUtil.i("OldMainActivityPeer.onHandleIntent", "clearing all new voicemails"); 369 CallLogNotificationsService.markAllNewVoicemailsAsOld(mainActivity); 370 } 371 } 372 373 /** Returns true if the given intent is a Dial intent with data or an Add Call intent. */ 374 private boolean isDialOrAddCallIntent(Intent intent) { 375 if (intent == null) { 376 return false; 377 } 378 379 if (Intent.ACTION_DIAL.equals(intent.getAction())) { 380 return true; 381 } 382 383 if (Intent.ACTION_VIEW.equals(intent.getAction())) { 384 Uri data = intent.getData(); 385 if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) { 386 return true; 387 } 388 } 389 return DialpadFragment.isAddCallMode(intent); 390 } 391 392 @Override 393 public void onActivityResume() { 394 callLogFragmentListener.onActivityResume(); 395 // Start the thread that updates the smart dial database if the activity is recreated with a 396 // language change. 397 boolean forceUpdate = 398 !CompatUtils.getLocale(mainActivity).getISO3Language().equals(savedLanguageCode); 399 Database.get(mainActivity) 400 .getDatabaseHelper(mainActivity) 401 .startSmartDialUpdateThread(forceUpdate); 402 showPostCallPrompt(); 403 404 if (searchController.isInSearch() 405 || callLogAdapterOnActionModeStateChangedListener.isActionModeStateEnabled()) { 406 bottomNav.setVisibility(View.GONE); 407 } else { 408 bottomNav.setVisibility(View.VISIBLE); 409 } 410 411 // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. 412 ThreadUtil.postDelayedOnUiThread( 413 () -> 414 MetricsComponent.get(mainActivity) 415 .metrics() 416 .recordMemory(Metrics.OLD_MAIN_ACTIVITY_PEER_ON_RESUME_MEMORY_EVENT_NAME), 417 1000); 418 } 419 420 @Override 421 public void onUserLeaveHint() { 422 searchController.onUserLeaveHint(); 423 } 424 425 @Override 426 public void onActivityPause() {} 427 428 @Override 429 public void onActivityStop() { 430 lastTabController.onActivityStop(); 431 callLogFragmentListener.onActivityStop( 432 mainActivity.isChangingConfigurations(), 433 mainActivity.getSystemService(KeyguardManager.class).isKeyguardLocked()); 434 } 435 436 @Override 437 public void onActivityDestroyed() {} 438 439 private void showPostCallPrompt() { 440 if (TelecomUtil.isInManagedCall(mainActivity)) { 441 // No prompt to show if the user is in a call 442 return; 443 } 444 445 if (searchController.isInSearch()) { 446 // Don't show the prompt if we're in the search ui 447 return; 448 } 449 450 PostCall.promptUserForMessageIfNecessary(mainActivity, snackbarContainer); 451 } 452 453 @Override 454 public void onSaveInstanceState(Bundle bundle) { 455 bundle.putString( 456 KEY_SAVED_LANGUAGE_CODE, CompatUtils.getLocale(mainActivity).getISO3Language()); 457 bundle.putInt(KEY_CURRENT_TAB, bottomNav.getSelectedTab()); 458 searchController.onSaveInstanceState(bundle); 459 } 460 461 @Override 462 public void onActivityResult(int requestCode, int resultCode, Intent data) { 463 LogUtil.i( 464 "OldMainActivityPeer.onActivityResult", 465 "requestCode:%d, resultCode:%d", 466 requestCode, 467 resultCode); 468 if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) { 469 searchController.onVoiceResults(resultCode, data); 470 } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_COMPOSER) { 471 if (resultCode == AppCompatActivity.RESULT_FIRST_USER) { 472 LogUtil.i( 473 "OldMainActivityPeer.onActivityResult", "returned from call composer, error occurred"); 474 String message = 475 mainActivity.getString( 476 R.string.call_composer_connection_failed, 477 data.getStringExtra(CallComposerActivity.KEY_CONTACT_NAME)); 478 Snackbar.make(snackbarContainer, message, Snackbar.LENGTH_LONG).show(); 479 } else { 480 LogUtil.i("OldMainActivityPeer.onActivityResult", "returned from call composer, no error"); 481 } 482 483 } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_DETAILS) { 484 if (resultCode == AppCompatActivity.RESULT_OK 485 && data != null 486 && data.getBooleanExtra(CallDetailsActivity.EXTRA_HAS_ENRICHED_CALL_DATA, false)) { 487 String number = data.getStringExtra(CallDetailsActivity.EXTRA_PHONE_NUMBER); 488 int snackbarDurationMillis = 5_000; 489 Snackbar.make( 490 snackbarContainer, 491 mainActivity.getString(R.string.ec_data_deleted), 492 snackbarDurationMillis) 493 .setAction( 494 R.string.view_conversation, 495 v -> 496 mainActivity.startActivity( 497 IntentProvider.getSendSmsIntentProvider(number).getIntent(mainActivity))) 498 .setActionTextColor( 499 ContextCompat.getColor(mainActivity, R.color.dialer_snackbar_action_text_color)) 500 .show(); 501 } 502 503 } else if (requestCode == ActivityRequestCodes.DIALTACTS_DUO) { 504 // We just returned from starting Duo for a task. Reload our reachability data since it 505 // may have changed after a user finished activating Duo. 506 DuoComponent.get(mainActivity).getDuo().reloadReachability(mainActivity); 507 508 } else { 509 LogUtil.e("OldMainActivityPeer.onActivityResult", "Unknown request code: " + requestCode); 510 } 511 } 512 513 @Override 514 public boolean onBackPressed() { 515 LogUtil.enterBlock("OldMainActivityPeer.onBackPressed"); 516 if (searchController.onBackPressed()) { 517 return true; 518 } 519 return false; 520 } 521 522 @Nullable 523 @Override 524 @SuppressWarnings("unchecked") // Casts are checked using runtime methods 525 public <T> T getImpl(Class<T> callbackInterface) { 526 if (callbackInterface.isInstance(onContactSelectedListener)) { 527 return (T) onContactSelectedListener; 528 } else if (callbackInterface.isInstance(onDialpadQueryChangedListener)) { 529 return (T) onDialpadQueryChangedListener; 530 } else if (callbackInterface.isInstance(dialpadListener)) { 531 return (T) dialpadListener; 532 } else if (callbackInterface.isInstance(dialpadFragmentHostInterface)) { 533 return (T) dialpadFragmentHostInterface; 534 } else if (callbackInterface.isInstance(searchFragmentListener)) { 535 return (T) searchFragmentListener; 536 } else if (callbackInterface.isInstance(callLogAdapterOnActionModeStateChangedListener)) { 537 return (T) callLogAdapterOnActionModeStateChangedListener; 538 } else if (callbackInterface.isInstance(callLogHostInterface)) { 539 return (T) callLogHostInterface; 540 } else if (callbackInterface.isInstance(callLogFragmentListener)) { 541 return (T) callLogFragmentListener; 542 } else if (callbackInterface.isInstance(onListFragmentScrolledListener)) { 543 return (T) onListFragmentScrolledListener; 544 } else if (callbackInterface.isInstance(onPhoneNumberPickerActionListener)) { 545 return (T) onPhoneNumberPickerActionListener; 546 } else if (callbackInterface.isInstance(oldSpeedDialFragmentHost)) { 547 return (T) oldSpeedDialFragmentHost; 548 } else if (callbackInterface.isInstance(searchController)) { 549 return (T) searchController; 550 } else { 551 return null; 552 } 553 } 554 555 public MainSearchController getNewMainSearchController( 556 BottomNavBar bottomNavBar, 557 FloatingActionButton fab, 558 MainToolbar mainToolbar, 559 View toolbarShadow) { 560 return new MainSearchController(mainActivity, bottomNavBar, fab, mainToolbar, toolbarShadow); 561 } 562 563 public MainOnDialpadQueryChangedListener getNewOnDialpadQueryChangedListener( 564 MainSearchController mainSearchController) { 565 return new MainOnDialpadQueryChangedListener(mainSearchController); 566 } 567 568 /** @see OnContactSelectedListener */ 569 private static final class MainOnContactSelectedListener implements OnContactSelectedListener { 570 571 private final Context context; 572 573 MainOnContactSelectedListener(Context context) { 574 this.context = context; 575 } 576 577 @Override 578 public void onContactSelected(ImageView photo, Uri contactUri, long contactId) { 579 // TODO(calderwoodra): Add impression logging 580 QuickContact.showQuickContact( 581 context, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */); 582 } 583 } 584 585 /** @see OnDialpadQueryChangedListener */ 586 protected static class MainOnDialpadQueryChangedListener 587 implements OnDialpadQueryChangedListener { 588 589 private final MainSearchController searchController; 590 591 protected MainOnDialpadQueryChangedListener(MainSearchController searchController) { 592 this.searchController = searchController; 593 } 594 595 @Override 596 public void onDialpadQueryChanged(String query) { 597 searchController.onDialpadQueryChanged(query); 598 } 599 } 600 601 /** @see DialpadListener */ 602 private static final class MainDialpadListener implements DialpadListener { 603 604 private final MainSearchController searchController; 605 private final Context context; 606 private final UiListener<String> listener; 607 608 MainDialpadListener( 609 Context context, MainSearchController searchController, UiListener<String> uiListener) { 610 this.context = context; 611 this.searchController = searchController; 612 this.listener = uiListener; 613 } 614 615 @Override 616 public void getLastOutgoingCall(LastOutgoingCallCallback callback) { 617 ListenableFuture<String> listenableFuture = 618 DialerExecutorComponent.get(context) 619 .backgroundExecutor() 620 .submit(() -> Calls.getLastOutgoingCall(context)); 621 listener.listen(context, listenableFuture, callback::lastOutgoingCall, throwable -> {}); 622 } 623 624 @Override 625 public void onDialpadShown() { 626 searchController.onDialpadShown(); 627 } 628 629 @Override 630 public void onCallPlacedFromDialpad() { 631 // TODO(calderwoodra): logging 632 searchController.onCallPlacedFromSearch(); 633 } 634 } 635 636 /** @see SearchFragmentListener */ 637 private static final class MainSearchFragmentListener implements SearchFragmentListener { 638 639 private final MainSearchController searchController; 640 641 MainSearchFragmentListener(MainSearchController searchController) { 642 this.searchController = searchController; 643 } 644 645 @Override 646 public void onSearchListTouch() { 647 searchController.onSearchListTouch(); 648 } 649 650 @Override 651 public void onCallPlacedFromSearch() { 652 // TODO(calderwoodra): logging 653 searchController.onCallPlacedFromSearch(); 654 } 655 } 656 657 /** @see DialpadFragment.HostInterface */ 658 private static final class MainDialpadFragmentHost implements DialpadFragment.HostInterface { 659 660 @Override 661 public boolean onDialpadSpacerTouchWithEmptyQuery() { 662 // No-op, just let the clicks fall through to the search list 663 return false; 664 } 665 666 @Override 667 public boolean shouldShowDialpadChooser() { 668 // Never show the dialpad chooser. Ever. 669 return false; 670 } 671 } 672 673 /** @see CallLogAdapter.OnActionModeStateChangedListener */ 674 private static final class MainCallLogAdapterOnActionModeStateChangedListener 675 implements CallLogAdapter.OnActionModeStateChangedListener { 676 677 private boolean isEnabled; 678 679 @Override 680 public void onActionModeStateChanged(boolean isEnabled) { 681 this.isEnabled = isEnabled; 682 } 683 684 @Override 685 public boolean isActionModeStateEnabled() { 686 return isEnabled; 687 } 688 } 689 690 /** @see CallLogFragment.HostInterface */ 691 private static final class MainCallLogHost implements CallLogFragment.HostInterface { 692 693 private final FloatingActionButton fab; 694 private final MainSearchController searchController; 695 696 MainCallLogHost(MainSearchController searchController, FloatingActionButton fab) { 697 this.searchController = searchController; 698 this.fab = fab; 699 } 700 701 @Override 702 public void showDialpad() { 703 searchController.showDialpad(true); 704 } 705 706 @Override 707 public void enableFloatingButton(boolean enabled) { 708 LogUtil.i("MainCallLogHost.enableFloatingButton", "enabled: " + enabled); 709 if (enabled) { 710 fab.show(); 711 } else { 712 fab.hide(); 713 } 714 } 715 } 716 717 /** 718 * Handles the logic for callbacks from: 719 * 720 * <ul> 721 * <li>{@link CallLogFragment} 722 * <li>{@link CallLogQueryHandler} 723 * <li>{@link BottomNavBar} 724 * </ul> 725 * 726 * This mainly entails: 727 * 728 * <ul> 729 * <li>Handling querying for missed calls/unread voicemails. 730 * <li>Displaying a badge to the user in the bottom nav when there are missed calls/unread 731 * voicemails present. 732 * <li>Marking missed calls as read when appropriate. See {@link 733 * #markMissedCallsAsReadAndRemoveNotification()} 734 * <li>TODO(calderwoodra): multiselect 735 * </ul> 736 * 737 * @see CallLogFragmentListener 738 * @see CallLogQueryHandler.Listener 739 * @see OnBottomNavTabSelectedListener 740 */ 741 private static final class MainCallLogFragmentListener 742 implements CallLogFragmentListener, 743 CallLogQueryHandler.Listener, 744 OnBottomNavTabSelectedListener { 745 746 private final CallLogQueryHandler callLogQueryHandler; 747 private final Context context; 748 private final BottomNavBar bottomNavBar; 749 private final Toolbar toolbar; 750 751 private @TabIndex int currentTab = TabIndex.SPEED_DIAL; 752 private long timeSelected = -1; 753 private boolean activityIsAlive; 754 755 private final ContentObserver voicemailStatusObserver = 756 new ContentObserver(new Handler()) { 757 @Override 758 public void onChange(boolean selfChange) { 759 super.onChange(selfChange); 760 callLogQueryHandler.fetchVoicemailStatus(); 761 } 762 }; 763 764 MainCallLogFragmentListener( 765 Context context, 766 ContentResolver contentResolver, 767 BottomNavBar bottomNavBar, 768 Toolbar toolbar) { 769 callLogQueryHandler = new CallLogQueryHandler(context, contentResolver, this); 770 this.context = context; 771 this.bottomNavBar = bottomNavBar; 772 this.toolbar = toolbar; 773 } 774 775 private void registerVoicemailStatusContentObserver(Context context) { 776 777 if (PermissionsUtil.hasReadVoicemailPermissions(context) 778 && PermissionsUtil.hasAddVoicemailPermissions(context)) { 779 context 780 .getContentResolver() 781 .registerContentObserver( 782 VoicemailContract.Status.CONTENT_URI, true, voicemailStatusObserver); 783 } else { 784 LogUtil.w( 785 "MainCallLogFragmentListener.registerVoicemailStatusContentObserver", 786 "no voicemail read/add permissions"); 787 } 788 } 789 790 @Override 791 public void updateTabUnreadCounts() { 792 callLogQueryHandler.fetchMissedCallsUnreadCount(); 793 callLogQueryHandler.fetchVoicemailUnreadCount(); 794 } 795 796 @Override 797 public void showMultiSelectRemoveView(boolean show) { 798 bottomNavBar.setVisibility(show ? View.GONE : View.VISIBLE); 799 toolbar.setVisibility(show ? View.GONE : View.VISIBLE); 800 } 801 802 @Override 803 public void onVoicemailStatusFetched(Cursor statusCursor) { 804 LogUtil.i("OldMainActivityPeer.MainCallLogFragmentListener", "onVoicemailStatusFetched"); 805 VoicemailStatusCorruptionHandler.maybeFixVoicemailStatus( 806 context, statusCursor, Source.Activity); 807 808 // Update hasActiveVoicemailProvider, which controls the number of tabs displayed. 809 int numberOfActiveVoicemailSources = 810 VoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor); 811 812 boolean hasActiveVoicemailProvider = numberOfActiveVoicemailSources > 0; 813 LogUtil.i( 814 "OldMainActivityPeer.onVoicemailStatusFetched", 815 String.format( 816 Locale.US, 817 "hasActiveVoicemailProvider:%b, number of active voicemail sources:%d", 818 hasActiveVoicemailProvider, 819 numberOfActiveVoicemailSources)); 820 821 if (hasActiveVoicemailProvider) { 822 // TODO(yueg): Use new logging for VVM_TAB_VISIBLE 823 // Logger.get(context).logImpression(DialerImpression.Type.VVM_TAB_VISIBLE); 824 bottomNavBar.showVoicemail(true); 825 callLogQueryHandler.fetchVoicemailUnreadCount(); 826 } else { 827 bottomNavBar.showVoicemail(false); 828 } 829 830 StorageComponent.get(context) 831 .unencryptedSharedPrefs() 832 .edit() 833 .putBoolean( 834 VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, 835 hasActiveVoicemailProvider) 836 .apply(); 837 838 // TODO(uabdullah): Check if we need to force move to the VM tab (e.g in the event of 839 // clicking a vm notification and a status wasn't yet fetched). 840 } 841 842 @Override 843 public void onVoicemailUnreadCountFetched(Cursor cursor) { 844 if (activityIsAlive) { 845 bottomNavBar.setNotificationCount(TabIndex.VOICEMAIL, cursor.getCount()); 846 } 847 cursor.close(); 848 } 849 850 @Override 851 public void onMissedCallsUnreadCountFetched(Cursor cursor) { 852 if (activityIsAlive) { 853 bottomNavBar.setNotificationCount(TabIndex.CALL_LOG, cursor.getCount()); 854 } 855 cursor.close(); 856 } 857 858 @Override 859 public boolean onCallsFetched(Cursor combinedCursor) { 860 // Return false; did not take ownership of cursor 861 return false; 862 } 863 864 @Override 865 public void onSpeedDialSelected() { 866 setCurrentTab(TabIndex.SPEED_DIAL); 867 } 868 869 @Override 870 public void onCallLogSelected() { 871 setCurrentTab(TabIndex.CALL_LOG); 872 } 873 874 @Override 875 public void onContactsSelected() { 876 setCurrentTab(TabIndex.CONTACTS); 877 } 878 879 @Override 880 public void onVoicemailSelected() { 881 setCurrentTab(TabIndex.VOICEMAIL); 882 } 883 884 private void markMissedCallsAsReadAndRemoveNotification() { 885 callLogQueryHandler.markMissedCallsAsRead(); 886 CallLogNotificationsService.cancelAllMissedCalls(context); 887 } 888 889 private void setCurrentTab(@TabIndex int tabIndex) { 890 if (currentTab == TabIndex.CALL_LOG && tabIndex != TabIndex.CALL_LOG) { 891 markMissedCallsAsReadAndRemoveNotification(); 892 } 893 currentTab = tabIndex; 894 timeSelected = System.currentTimeMillis(); 895 } 896 897 public void onActivityResume() { 898 activityIsAlive = true; 899 registerVoicemailStatusContentObserver(context); 900 callLogQueryHandler.fetchVoicemailStatus(); 901 callLogQueryHandler.fetchMissedCallsUnreadCount(); 902 // Reset the tab on resume to restart the timer 903 setCurrentTab(bottomNavBar.getSelectedTab()); 904 } 905 906 /** Should be called when {@link Activity#onStop()} is called. */ 907 public void onActivityStop(boolean changingConfigurations, boolean keyguardLocked) { 908 context.getContentResolver().unregisterContentObserver(voicemailStatusObserver); 909 activityIsAlive = false; 910 if (viewedCallLogTabPastTimeThreshold() && !changingConfigurations && !keyguardLocked) { 911 markMissedCallsAsReadAndRemoveNotification(); 912 } 913 } 914 915 /** 916 * Returns true if the user has been (and still is) on the history tab for long than the 917 * threshold. 918 */ 919 private boolean viewedCallLogTabPastTimeThreshold() { 920 return currentTab == TabIndex.CALL_LOG 921 && timeSelected != -1 922 && System.currentTimeMillis() - timeSelected > TimeUnit.SECONDS.toMillis(3); 923 } 924 } 925 926 /** @see OnListFragmentScrolledListener */ 927 private static final class MainOnListFragmentScrolledListener 928 implements OnListFragmentScrolledListener { 929 930 private final View parentLayout; 931 932 MainOnListFragmentScrolledListener(View parentLayout) { 933 this.parentLayout = parentLayout; 934 } 935 936 @Override 937 public void onListFragmentScrollStateChange(int scrollState) { 938 DialerUtils.hideInputMethod(parentLayout); 939 } 940 941 @Override 942 public void onListFragmentScroll( 943 int firstVisibleItem, int visibleItemCount, int totalItemCount) { 944 // TODO: No-op for now. This should eventually show/hide the actionBar based on 945 // interactions with the ListsFragments. 946 } 947 } 948 949 /** @see OnPhoneNumberPickerActionListener */ 950 private static final class MainOnPhoneNumberPickerActionListener 951 implements OnPhoneNumberPickerActionListener { 952 953 private final TransactionSafeActivity activity; 954 955 MainOnPhoneNumberPickerActionListener(TransactionSafeActivity activity) { 956 this.activity = activity; 957 } 958 959 @Override 960 public void onPickDataUri( 961 Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData) { 962 PhoneNumberInteraction.startInteractionForPhoneCall( 963 activity, dataUri, isVideoCall, callSpecificAppData); 964 } 965 966 @Override 967 public void onPickPhoneNumber( 968 String phoneNumber, boolean isVideoCall, CallSpecificAppData callSpecificAppData) { 969 if (phoneNumber == null) { 970 // Invalid phone number, but let the call go through so that InCallUI can show 971 // an error message. 972 phoneNumber = ""; 973 } 974 PreCall.start( 975 activity, 976 new CallIntentBuilder(phoneNumber, callSpecificAppData) 977 .setIsVideoCall(isVideoCall) 978 .setAllowAssistedDial(callSpecificAppData.getAllowAssistedDialing())); 979 } 980 981 @Override 982 public void onHomeInActionBarSelected() { 983 // TODO(calderwoodra): investigate if we need to exit search here 984 // PhoneNumberPickerFragment#onOptionsItemSelected 985 } 986 } 987 988 /** 989 * Handles the callbacks for {@link OldSpeedDialFragment} and drag/drop logic for drag to remove. 990 * 991 * @see OldSpeedDialFragment.HostInterface 992 * @see OnDragDropListener 993 */ 994 private static final class MainOldSpeedDialFragmentHost 995 implements OldSpeedDialFragment.HostInterface, OnDragDropListener { 996 997 private final BottomNavBar bottomNavBar; 998 private final ImageView dragShadowOverlay; 999 private final RemoveView removeView; 1000 private final View searchViewContainer; 1001 private final MainToolbar toolbar; 1002 1003 // TODO(calderwoodra): Use this for drag and drop 1004 @SuppressWarnings("unused") 1005 private DragDropController dragDropController; 1006 1007 MainOldSpeedDialFragmentHost( 1008 BottomNavBar bottomNavBar, 1009 ImageView dragShadowOverlay, 1010 RemoveView removeView, 1011 View searchViewContainer, 1012 MainToolbar toolbar) { 1013 this.bottomNavBar = bottomNavBar; 1014 this.dragShadowOverlay = dragShadowOverlay; 1015 this.removeView = removeView; 1016 this.searchViewContainer = searchViewContainer; 1017 this.toolbar = toolbar; 1018 } 1019 1020 @Override 1021 public void setDragDropController(DragDropController dragDropController) { 1022 removeView.setDragDropController(dragDropController); 1023 } 1024 1025 @Override 1026 public void showAllContactsTab() { 1027 bottomNavBar.selectTab(TabIndex.CONTACTS); 1028 } 1029 1030 @Override 1031 public ImageView getDragShadowOverlay() { 1032 return dragShadowOverlay; 1033 } 1034 1035 @Override 1036 public void setHasFrequents(boolean hasFrequents) { 1037 toolbar.showClearFrequents(hasFrequents); 1038 } 1039 1040 @Override 1041 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 1042 showRemoveView(true); 1043 } 1044 1045 @Override 1046 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {} 1047 1048 @Override 1049 public void onDragFinished(int x, int y) { 1050 showRemoveView(false); 1051 } 1052 1053 @Override 1054 public void onDroppedOnRemove() {} 1055 1056 private void showRemoveView(boolean show) { 1057 if (show) { 1058 AnimUtils.crossFadeViews(removeView, searchViewContainer, 300); 1059 } else { 1060 AnimUtils.crossFadeViews(searchViewContainer, removeView, 300); 1061 } 1062 } 1063 } 1064 1065 /** 1066 * Implementation of {@link OnBottomNavTabSelectedListener} that handles logic for showing each of 1067 * the main tabs and FAB. 1068 * 1069 * <p>TODO(calderwoodra, uabdullah): Rethink the logic for showing/hiding the FAB when new 1070 * voicemail is ready. 1071 */ 1072 private static final class MainBottomNavBarBottomNavTabListener 1073 implements OnBottomNavTabSelectedListener { 1074 1075 private static final String SPEED_DIAL_TAG = "speed_dial"; 1076 private static final String CALL_LOG_TAG = "call_log"; 1077 private static final String CONTACTS_TAG = "contacts"; 1078 private static final String VOICEMAIL_TAG = "voicemail"; 1079 1080 private final Context context; 1081 private final FragmentManager fragmentManager; 1082 private final FloatingActionButton fab; 1083 1084 @TabIndex private int selectedTab = -1; 1085 1086 private MainBottomNavBarBottomNavTabListener( 1087 Context context, FragmentManager fragmentManager, FloatingActionButton fab) { 1088 this.context = context; 1089 this.fragmentManager = fragmentManager; 1090 this.fab = fab; 1091 preloadCallLogFragment(); 1092 } 1093 1094 private void preloadCallLogFragment() { 1095 if (ConfigProviderBindings.get(context).getBoolean("nui_preload_call_log", true)) { 1096 CallLogFragment fragment = new CallLogFragment(); 1097 fragmentManager 1098 .beginTransaction() 1099 .add(R.id.fragment_container, fragment, CALL_LOG_TAG) 1100 .hide(fragment) 1101 .commit(); 1102 } 1103 } 1104 1105 @Override 1106 public void onSpeedDialSelected() { 1107 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onSpeedDialSelected"); 1108 if (selectedTab != TabIndex.SPEED_DIAL) { 1109 Logger.get(context).logImpression(DialerImpression.Type.MAIN_SWITCH_TAB_TO_FAVORITE); 1110 selectedTab = TabIndex.SPEED_DIAL; 1111 } 1112 hideAllFragments(); 1113 Fragment fragment = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG); 1114 if (fragment == null) { 1115 fragmentManager 1116 .beginTransaction() 1117 .add(R.id.fragment_container, new OldSpeedDialFragment(), SPEED_DIAL_TAG) 1118 .commit(); 1119 } else { 1120 fragmentManager.beginTransaction().show(fragment).commit(); 1121 } 1122 fab.show(); 1123 } 1124 1125 @Override 1126 public void onCallLogSelected() { 1127 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onCallLogSelected"); 1128 if (selectedTab != TabIndex.CALL_LOG) { 1129 Logger.get(context).logImpression(DialerImpression.Type.MAIN_SWITCH_TAB_TO_CALL_LOG); 1130 selectedTab = TabIndex.CALL_LOG; 1131 } 1132 hideAllFragments(); 1133 CallLogFragment fragment = (CallLogFragment) fragmentManager.findFragmentByTag(CALL_LOG_TAG); 1134 if (fragment == null) { 1135 fragmentManager 1136 .beginTransaction() 1137 .add(R.id.fragment_container, new CallLogFragment(), CALL_LOG_TAG) 1138 .commit(); 1139 } else { 1140 fragmentManager.beginTransaction().show(fragment).commit(); 1141 } 1142 fab.show(); 1143 } 1144 1145 @Override 1146 public void onContactsSelected() { 1147 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onContactsSelected"); 1148 if (selectedTab != TabIndex.CONTACTS) { 1149 Logger.get(context).logImpression(DialerImpression.Type.MAIN_SWITCH_TAB_TO_CONTACTS); 1150 selectedTab = TabIndex.CONTACTS; 1151 } 1152 hideAllFragments(); 1153 ContactsFragment fragment = 1154 (ContactsFragment) fragmentManager.findFragmentByTag(CONTACTS_TAG); 1155 if (fragment == null) { 1156 fragmentManager 1157 .beginTransaction() 1158 .add( 1159 R.id.fragment_container, 1160 ContactsFragment.newInstance(Header.ADD_CONTACT), 1161 CONTACTS_TAG) 1162 .commit(); 1163 } else { 1164 fragmentManager.beginTransaction().show(fragment).commit(); 1165 } 1166 fab.show(); 1167 } 1168 1169 @Override 1170 public void onVoicemailSelected() { 1171 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onVoicemailSelected"); 1172 if (selectedTab != TabIndex.VOICEMAIL) { 1173 Logger.get(context).logImpression(DialerImpression.Type.MAIN_SWITCH_TAB_TO_VOICEMAIL); 1174 selectedTab = TabIndex.VOICEMAIL; 1175 } 1176 hideAllFragments(); 1177 VisualVoicemailCallLogFragment fragment = 1178 (VisualVoicemailCallLogFragment) fragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1179 if (fragment == null) { 1180 fragment = new VisualVoicemailCallLogFragment(); 1181 fragmentManager 1182 .beginTransaction() 1183 .add(R.id.fragment_container, fragment, VOICEMAIL_TAG) 1184 .commit(); 1185 } else { 1186 fragmentManager.beginTransaction().show(fragment).commit(); 1187 } 1188 fragment.setUserVisibleHint(true); 1189 fragment.onVisible(); 1190 } 1191 1192 private void hideAllFragments() { 1193 android.app.FragmentTransaction transaction = fragmentManager.beginTransaction(); 1194 if (fragmentManager.findFragmentByTag(SPEED_DIAL_TAG) != null) { 1195 transaction.hide(fragmentManager.findFragmentByTag(SPEED_DIAL_TAG)); 1196 } 1197 if (fragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) { 1198 // Old CallLogFragment 1199 transaction.hide(fragmentManager.findFragmentByTag(CALL_LOG_TAG)); 1200 } 1201 if (fragmentManager.findFragmentByTag(CONTACTS_TAG) != null) { 1202 transaction.hide(fragmentManager.findFragmentByTag(CONTACTS_TAG)); 1203 } 1204 if (fragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) { 1205 // Old VisualVoicemailFragment 1206 VisualVoicemailCallLogFragment fragment = 1207 (VisualVoicemailCallLogFragment) fragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1208 fragment.setUserVisibleHint(false); 1209 fragment.onNotVisible(); 1210 transaction.hide(fragment); 1211 } 1212 transaction.commit(); 1213 } 1214 } 1215 1216 private static final class LastTabController { 1217 1218 private final Context context; 1219 private final BottomNavBar bottomNavBar; 1220 private final boolean isEnabled; 1221 private final boolean canShowVoicemailTab; 1222 1223 LastTabController(Context context, BottomNavBar bottomNavBar, boolean canShowVoicemailTab) { 1224 this.context = context; 1225 this.bottomNavBar = bottomNavBar; 1226 isEnabled = ConfigProviderBindings.get(context).getBoolean("last_tab_enabled", false); 1227 this.canShowVoicemailTab = canShowVoicemailTab; 1228 } 1229 1230 /** Sets the last tab if the feature is enabled, otherwise defaults to speed dial. */ 1231 void selectLastTab() { 1232 @TabIndex int tabIndex = TabIndex.SPEED_DIAL; 1233 if (isEnabled) { 1234 tabIndex = 1235 StorageComponent.get(context) 1236 .unencryptedSharedPrefs() 1237 .getInt(KEY_LAST_TAB, TabIndex.SPEED_DIAL); 1238 } 1239 1240 // If the voicemail tab cannot be shown, default to showing speed dial 1241 if (tabIndex == TabIndex.VOICEMAIL && !canShowVoicemailTab) { 1242 tabIndex = TabIndex.SPEED_DIAL; 1243 } 1244 1245 bottomNavBar.selectTab(tabIndex); 1246 } 1247 1248 void onActivityStop() { 1249 StorageComponent.get(context) 1250 .unencryptedSharedPrefs() 1251 .edit() 1252 .putInt(KEY_LAST_TAB, bottomNavBar.getSelectedTab()) 1253 .apply(); 1254 } 1255 } 1256 } 1257