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.annotation.SuppressLint; 20 import android.app.Fragment; 21 import android.app.FragmentManager; 22 import android.app.FragmentTransaction; 23 import android.app.KeyguardManager; 24 import android.content.BroadcastReceiver; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.database.ContentObserver; 30 import android.database.Cursor; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.provider.CallLog.Calls; 35 import android.provider.ContactsContract.QuickContact; 36 import android.provider.VoicemailContract; 37 import android.support.annotation.NonNull; 38 import android.support.annotation.Nullable; 39 import android.support.design.widget.BottomSheetBehavior; 40 import android.support.design.widget.FloatingActionButton; 41 import android.support.design.widget.Snackbar; 42 import android.support.v4.content.ContextCompat; 43 import android.support.v4.content.LocalBroadcastManager; 44 import android.support.v7.app.AppCompatActivity; 45 import android.support.v7.widget.Toolbar; 46 import android.telecom.PhoneAccount; 47 import android.telecom.PhoneAccountHandle; 48 import android.telephony.TelephonyManager; 49 import android.text.TextUtils; 50 import android.text.method.LinkMovementMethod; 51 import android.view.ActionMode; 52 import android.view.DragEvent; 53 import android.view.View; 54 import android.view.ViewGroup; 55 import android.widget.ImageView; 56 import android.widget.TextView; 57 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; 58 import com.android.dialer.animation.AnimUtils; 59 import com.android.dialer.app.MainComponent; 60 import com.android.dialer.app.calllog.CallLogAdapter; 61 import com.android.dialer.app.calllog.CallLogFragment; 62 import com.android.dialer.app.calllog.CallLogFragment.CallLogFragmentListener; 63 import com.android.dialer.app.calllog.CallLogNotificationsService; 64 import com.android.dialer.app.calllog.IntentProvider; 65 import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment; 66 import com.android.dialer.app.list.DragDropController; 67 import com.android.dialer.app.list.OldSpeedDialFragment; 68 import com.android.dialer.app.list.OnDragDropListener; 69 import com.android.dialer.app.list.OnListFragmentScrolledListener; 70 import com.android.dialer.app.list.PhoneFavoriteSquareTileView; 71 import com.android.dialer.app.list.RemoveView; 72 import com.android.dialer.callcomposer.CallComposerActivity; 73 import com.android.dialer.calldetails.OldCallDetailsActivity; 74 import com.android.dialer.callintent.CallIntentBuilder; 75 import com.android.dialer.callintent.CallSpecificAppData; 76 import com.android.dialer.calllog.CallLogComponent; 77 import com.android.dialer.calllog.config.CallLogConfigComponent; 78 import com.android.dialer.calllog.ui.NewCallLogFragment; 79 import com.android.dialer.common.FragmentUtils.FragmentUtilListener; 80 import com.android.dialer.common.LogUtil; 81 import com.android.dialer.common.concurrent.DefaultFutureCallback; 82 import com.android.dialer.common.concurrent.DialerExecutorComponent; 83 import com.android.dialer.common.concurrent.ThreadUtil; 84 import com.android.dialer.common.concurrent.UiListener; 85 import com.android.dialer.configprovider.ConfigProviderComponent; 86 import com.android.dialer.constants.ActivityRequestCodes; 87 import com.android.dialer.contactsfragment.ContactsFragment; 88 import com.android.dialer.contactsfragment.ContactsFragment.Header; 89 import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; 90 import com.android.dialer.database.CallLogQueryHandler; 91 import com.android.dialer.database.Database; 92 import com.android.dialer.dialpadview.DialpadFragment; 93 import com.android.dialer.dialpadview.DialpadFragment.DialpadListener; 94 import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback; 95 import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener; 96 import com.android.dialer.duo.DuoComponent; 97 import com.android.dialer.i18n.LocaleUtils; 98 import com.android.dialer.interactions.PhoneNumberInteraction; 99 import com.android.dialer.logging.DialerImpression; 100 import com.android.dialer.logging.Logger; 101 import com.android.dialer.logging.ScreenEvent; 102 import com.android.dialer.main.MainActivityPeer; 103 import com.android.dialer.main.impl.bottomnav.BottomNavBar; 104 import com.android.dialer.main.impl.bottomnav.BottomNavBar.OnBottomNavTabSelectedListener; 105 import com.android.dialer.main.impl.bottomnav.BottomNavBar.TabIndex; 106 import com.android.dialer.main.impl.bottomnav.MissedCallCountObserver; 107 import com.android.dialer.main.impl.toolbar.MainToolbar; 108 import com.android.dialer.metrics.Metrics; 109 import com.android.dialer.metrics.MetricsComponent; 110 import com.android.dialer.postcall.PostCall; 111 import com.android.dialer.precall.PreCall; 112 import com.android.dialer.promotion.Promotion; 113 import com.android.dialer.promotion.Promotion.PromotionType; 114 import com.android.dialer.promotion.PromotionComponent; 115 import com.android.dialer.searchfragment.list.NewSearchFragment.SearchFragmentListener; 116 import com.android.dialer.smartdial.util.SmartDialPrefix; 117 import com.android.dialer.speeddial.SpeedDialFragment; 118 import com.android.dialer.storage.StorageComponent; 119 import com.android.dialer.telecom.TelecomUtil; 120 import com.android.dialer.theme.base.Theme; 121 import com.android.dialer.theme.base.ThemeComponent; 122 import com.android.dialer.util.DialerUtils; 123 import com.android.dialer.util.PermissionsUtil; 124 import com.android.dialer.util.TransactionSafeActivity; 125 import com.android.dialer.voicemail.listui.NewVoicemailFragment; 126 import com.android.dialer.voicemail.listui.error.VoicemailStatusCorruptionHandler; 127 import com.android.dialer.voicemail.listui.error.VoicemailStatusCorruptionHandler.Source; 128 import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker; 129 import com.android.dialer.voicemailstatus.VoicemailStatusHelper; 130 import com.android.voicemail.VoicemailComponent; 131 import com.google.common.util.concurrent.Futures; 132 import com.google.common.util.concurrent.ListenableFuture; 133 import com.google.common.util.concurrent.MoreExecutors; 134 import java.util.Locale; 135 import java.util.Optional; 136 import java.util.concurrent.TimeUnit; 137 138 /** 139 * OldMainActivityPeer which implements all of the old fragments we know and love <3 140 * 141 * <p>TODO(calderwoodra): Deprecate this class when we launch NewmainActivityPeer. 142 */ 143 public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListener { 144 145 private static final String KEY_SAVED_LANGUAGE_CODE = "saved_language_code"; 146 private static final String KEY_CURRENT_TAB = "current_tab"; 147 private static final String KEY_LAST_TAB = "last_tab"; 148 149 /** Action and extra to let the activity know which tab to open up to. */ 150 private static final String ACTION_SHOW_TAB = "ACTION_SHOW_TAB"; 151 152 private static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB"; 153 154 // TODO(calderwoodra): change to AppCompatActivity once new speed dial ships 155 private final TransactionSafeActivity activity; 156 157 private final BroadcastReceiver disableCallLogFrameworkReceiver = 158 new BroadcastReceiver() { 159 @Override 160 public void onReceive(Context context, Intent intent) { 161 if (bottomNavTabListener == null) { 162 return; 163 } 164 /* 165 * Remove the NewCallLogFragment and NewVoicemailFragment if it is currently attached. If 166 * this is not done, user interaction with the fragment could cause call log framework 167 * state to be unexpectedly written. For example scrolling could cause the 168 * AnnotatedCallLog to be read (which would trigger database creation). 169 */ 170 bottomNavTabListener.disableNewCallLogFragment(); 171 bottomNavTabListener.disableNewVoicemailFragment(); 172 } 173 }; 174 175 // Contacts 176 private MainOnContactSelectedListener onContactSelectedListener; 177 178 // Dialpad and Search 179 private MainDialpadFragmentHost dialpadFragmentHostInterface; 180 private MainSearchController searchController; 181 private MainOnDialpadQueryChangedListener onDialpadQueryChangedListener; 182 private MainDialpadListener dialpadListener; 183 private MainSearchFragmentListener searchFragmentListener; 184 185 // Action Mode 186 private MainCallLogAdapterOnActionModeStateChangedListener 187 callLogAdapterOnActionModeStateChangedListener; 188 189 // Call Log 190 private MainCallLogHost callLogHostInterface; 191 private MainCallLogFragmentListener callLogFragmentListener; 192 private MainOnListFragmentScrolledListener onListFragmentScrolledListener; 193 194 // Speed Dial 195 private MainOnPhoneNumberPickerActionListener onPhoneNumberPickerActionListener; 196 private MainOldSpeedDialFragmentHost oldSpeedDialFragmentHost; 197 private MainSpeedDialFragmentHost speedDialFragmentHost; 198 199 /** Language the device was in last time {@link #onSaveInstanceState(Bundle)} was called. */ 200 private String savedLanguageCode; 201 202 private LastTabController lastTabController; 203 204 private BottomNavBar bottomNav; 205 private MainBottomNavBarBottomNavTabListener bottomNavTabListener; 206 private View snackbarContainer; 207 private MissedCallCountObserver missedCallCountObserver; 208 private UiListener<String> getLastOutgoingCallListener; 209 private UiListener<Integer> missedCallObserverUiListener; 210 private View bottomSheet; 211 getShowTabIntent(Context context, @TabIndex int tabIndex)212 public static Intent getShowTabIntent(Context context, @TabIndex int tabIndex) { 213 Intent intent = new Intent(context, MainActivity.class); 214 intent.setAction(ACTION_SHOW_TAB); 215 intent.putExtra(EXTRA_SHOW_TAB, tabIndex); 216 // TODO(calderwoodra): Do we need to set some URI data here 217 return intent; 218 } 219 isShowTabIntent(Intent intent)220 static boolean isShowTabIntent(Intent intent) { 221 return ACTION_SHOW_TAB.equals(intent.getAction()) && intent.hasExtra(EXTRA_SHOW_TAB); 222 } 223 getTabFromIntent(Intent intent)224 static @TabIndex int getTabFromIntent(Intent intent) { 225 return intent.getIntExtra(EXTRA_SHOW_TAB, -1); 226 } 227 OldMainActivityPeer(TransactionSafeActivity activity)228 public OldMainActivityPeer(TransactionSafeActivity activity) { 229 this.activity = activity; 230 } 231 232 @Override onActivityCreate(Bundle savedInstanceState)233 public void onActivityCreate(Bundle savedInstanceState) { 234 LogUtil.enterBlock("OldMainActivityPeer.onActivityCreate"); 235 setTheme(); 236 activity.setContentView(R.layout.main_activity); 237 initUiListeners(); 238 initLayout(savedInstanceState); 239 SmartDialPrefix.initializeNanpSettings(activity); 240 } 241 242 /** should be called before {@link AppCompatActivity#setContentView(int)}. */ setTheme()243 private void setTheme() { 244 @Theme.Type int theme = ThemeComponent.get(activity).theme().getTheme(); 245 switch (theme) { 246 case Theme.DARK: 247 activity.setTheme(R.style.MainActivityTheme_Dark); 248 break; 249 case Theme.LIGHT: 250 case Theme.LIGHT_M2: 251 activity.setTheme(R.style.MainActivityTheme); 252 break; 253 case Theme.UNKNOWN: 254 default: 255 throw new IllegalArgumentException("Invalid theme."); 256 } 257 } 258 initUiListeners()259 private void initUiListeners() { 260 getLastOutgoingCallListener = 261 DialerExecutorComponent.get(activity) 262 .createUiListener(activity.getFragmentManager(), "Query last phone number"); 263 missedCallObserverUiListener = 264 DialerExecutorComponent.get(activity) 265 .createUiListener(activity.getFragmentManager(), "Missed call observer"); 266 } 267 initLayout(Bundle savedInstanceState)268 private void initLayout(Bundle savedInstanceState) { 269 onContactSelectedListener = new MainOnContactSelectedListener(activity); 270 dialpadFragmentHostInterface = new MainDialpadFragmentHost(); 271 272 snackbarContainer = activity.findViewById(R.id.coordinator_layout); 273 bottomSheet = activity.findViewById(R.id.promotion_bottom_sheet); 274 BottomSheetBehavior<View> bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet); 275 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); 276 277 FloatingActionButton fab = activity.findViewById(R.id.fab); 278 fab.setOnClickListener( 279 v -> { 280 Logger.get(activity).logImpression(DialerImpression.Type.MAIN_CLICK_FAB_TO_OPEN_DIALPAD); 281 searchController.showDialpad(true); 282 if (callLogAdapterOnActionModeStateChangedListener.isEnabled) { 283 LogUtil.i("OldMainActivityPeer.onFabClicked", "closing multiselect"); 284 callLogAdapterOnActionModeStateChangedListener.actionMode.finish(); 285 } 286 }); 287 288 MainToolbar toolbar = activity.findViewById(R.id.toolbar); 289 toolbar.maybeShowSimulator(activity); 290 activity.setSupportActionBar(activity.findViewById(R.id.toolbar)); 291 292 bottomNav = activity.findViewById(R.id.bottom_nav_bar); 293 bottomNavTabListener = 294 new MainBottomNavBarBottomNavTabListener( 295 activity, 296 activity.getFragmentManager(), 297 activity.getSupportFragmentManager(), 298 fab, 299 bottomSheet); 300 bottomNav.addOnTabSelectedListener(bottomNavTabListener); 301 // TODO(uabdullah): Handle case of when a sim is inserted/removed while the activity is open. 302 boolean showVoicemailTab = canVoicemailTabBeShown(activity); 303 bottomNav.showVoicemail(showVoicemailTab); 304 305 missedCallCountObserver = 306 new MissedCallCountObserver( 307 activity.getApplicationContext(), bottomNav, missedCallObserverUiListener); 308 309 callLogFragmentListener = 310 new MainCallLogFragmentListener( 311 activity, activity.getContentResolver(), bottomNav, toolbar, bottomNavTabListener); 312 bottomNav.addOnTabSelectedListener(callLogFragmentListener); 313 314 searchController = 315 getNewMainSearchController( 316 bottomNav, fab, toolbar, activity.findViewById(R.id.toolbar_shadow), snackbarContainer); 317 toolbar.setSearchBarListener(searchController); 318 319 onDialpadQueryChangedListener = getNewOnDialpadQueryChangedListener(searchController); 320 dialpadListener = 321 new MainDialpadListener(activity, searchController, getLastOutgoingCallListener); 322 searchFragmentListener = new MainSearchFragmentListener(searchController); 323 callLogAdapterOnActionModeStateChangedListener = 324 new MainCallLogAdapterOnActionModeStateChangedListener(); 325 callLogHostInterface = new MainCallLogHost(searchController, fab); 326 327 onListFragmentScrolledListener = new MainOnListFragmentScrolledListener(snackbarContainer); 328 onPhoneNumberPickerActionListener = new MainOnPhoneNumberPickerActionListener(activity); 329 oldSpeedDialFragmentHost = 330 new MainOldSpeedDialFragmentHost( 331 activity, 332 activity.findViewById(R.id.root_layout), 333 bottomNav, 334 activity.findViewById(R.id.contact_tile_drag_shadow_overlay), 335 activity.findViewById(R.id.remove_view), 336 activity.findViewById(R.id.search_view_container), 337 toolbar); 338 speedDialFragmentHost = 339 new MainSpeedDialFragmentHost( 340 toolbar, 341 activity.findViewById(R.id.root_layout), 342 activity.findViewById(R.id.coordinator_layout), 343 activity.findViewById(R.id.fragment_container)); 344 345 lastTabController = new LastTabController(activity, bottomNav, showVoicemailTab); 346 347 // Restore our view state if needed, else initialize as if the app opened for the first time 348 if (savedInstanceState != null) { 349 savedLanguageCode = savedInstanceState.getString(KEY_SAVED_LANGUAGE_CODE); 350 searchController.onRestoreInstanceState(savedInstanceState); 351 bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB)); 352 } else { 353 onHandleIntent(activity.getIntent()); 354 } 355 } 356 357 /** 358 * Check and return whether the voicemail tab should be shown or not. This includes the following 359 * criteria under which we show the voicemail tab: 360 * <li>The voicemail number exists (e.g we are able to dial into listen to voicemail or press and 361 * hold 1) (TODO (uabdullah): Handle this case properly) 362 * <li>Visual voicemail is enabled from the settings tab 363 * <li>Visual voicemail carrier is supported by dialer 364 * <li>There is no voicemail carrier app installed. 365 * 366 * @param context 367 * @return return if voicemail tab should be shown or not depending on what the voicemail state is 368 * for the carrier. 369 */ canVoicemailTabBeShown(Context context)370 private static boolean canVoicemailTabBeShown(Context context) { 371 PhoneAccountHandle defaultUserSelectedAccount = 372 TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_VOICEMAIL); 373 374 if (!isVoicemailAvailable(context, defaultUserSelectedAccount)) { 375 LogUtil.i("OldMainActivityPeer.canVoicemailTabBeShown", "Voicemail is not available"); 376 return false; 377 } 378 379 if (VoicemailComponent.get(context) 380 .getVoicemailClient() 381 .isVoicemailEnabled(context, defaultUserSelectedAccount)) { 382 LogUtil.i("OldMainActivityPeer.canVoicemailTabBeShown", "Voicemail is enabled"); 383 return true; 384 } 385 LogUtil.i("OldMainActivityPeer.canVoicemailTabBeShown", "returning false"); 386 return false; 387 } 388 389 /** 390 * Check if voicemail is enabled/accessible. 391 * 392 * @return true if voicemail is enabled and accessible. Note that this can be false "temporarily" 393 * after the app boot e.g if the sim isn't fully recognized. TODO(uabdullah): Possibly add a 394 * listener of some kind to detect when a sim is recognized. TODO(uabdullah): Move this to a 395 * utility class or wrap it all in a static inner class. 396 */ 397 @SuppressLint("MissingPermission") isVoicemailAvailable( Context context, PhoneAccountHandle defaultUserSelectedAccount)398 private static boolean isVoicemailAvailable( 399 Context context, PhoneAccountHandle defaultUserSelectedAccount) { 400 401 if (!PermissionsUtil.hasReadPhoneStatePermissions(context)) { 402 LogUtil.i( 403 "OldMainActivityPeer.isVoicemailAvailable", 404 "No read phone permisison or not the default dialer."); 405 return false; 406 } 407 408 if (defaultUserSelectedAccount == null) { 409 // In a single-SIM phone, there is no default outgoing phone account selected by 410 // the user, so just call TelephonyManager#getVoicemailNumber directly. 411 return !TextUtils.isEmpty(getTelephonyManager(context).getVoiceMailNumber()); 412 } else { 413 return !TextUtils.isEmpty( 414 TelecomUtil.getVoicemailNumber(context, defaultUserSelectedAccount)); 415 } 416 } 417 getTelephonyManager(Context context)418 private static TelephonyManager getTelephonyManager(Context context) { 419 return context.getSystemService(TelephonyManager.class); 420 } 421 422 @Override onNewIntent(Intent intent)423 public void onNewIntent(Intent intent) { 424 LogUtil.enterBlock("OldMainActivityPeer.onNewIntent"); 425 onHandleIntent(intent); 426 } 427 onHandleIntent(Intent intent)428 private void onHandleIntent(Intent intent) { 429 // Some important implementation notes: 430 // 1) If the intent contains extra data to open to a specific screen (e.g. DIAL intent), when 431 // the user leaves that screen, they will return here and add see a blank screen unless we 432 // select a tab here. 433 // 2) Don't return early here in case the intent does contain extra data. 434 // 3) External intents should take priority over other intents (like Calls.CONTENT_TYPE). 435 @TabIndex int tabToSelect; 436 if (Calls.CONTENT_TYPE.equals(intent.getType())) { 437 Bundle extras = intent.getExtras(); 438 if (extras != null && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) { 439 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Voicemail content type intent"); 440 tabToSelect = TabIndex.VOICEMAIL; 441 Logger.get(activity).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CLICKED); 442 } else { 443 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Call log content type intent"); 444 tabToSelect = TabIndex.CALL_LOG; 445 } 446 447 } else if (isShowTabIntent(intent)) { 448 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Show tab intent"); 449 tabToSelect = getTabFromIntent(intent); 450 } else { 451 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Show last tab"); 452 tabToSelect = lastTabController.getLastTab(); 453 } 454 logImpressionForSelectedTab(tabToSelect); 455 bottomNav.selectTab(tabToSelect); 456 457 if (isDialOrAddCallIntent(intent)) { 458 LogUtil.i("OldMainActivityPeer.onHandleIntent", "Dial or add call intent"); 459 // Dialpad will grab the intent and populate the number 460 searchController.showDialpadFromNewIntent(); 461 Logger.get(activity).logImpression(DialerImpression.Type.MAIN_OPEN_WITH_DIALPAD); 462 } 463 464 if (intent.getBooleanExtra(MainComponent.EXTRA_CLEAR_NEW_VOICEMAILS, false)) { 465 LogUtil.i("OldMainActivityPeer.onHandleIntent", "clearing all new voicemails"); 466 CallLogNotificationsService.markAllNewVoicemailsAsOld(activity); 467 } 468 } 469 470 /** Log impression for non user tab selection. */ logImpressionForSelectedTab(@abIndex int tab)471 private void logImpressionForSelectedTab(@TabIndex int tab) { 472 if (tab == TabIndex.SPEED_DIAL) { 473 Logger.get(activity).logImpression(DialerImpression.Type.MAIN_OPEN_WITH_TAB_FAVORITE); 474 } else if (tab == TabIndex.CALL_LOG) { 475 Logger.get(activity).logImpression(DialerImpression.Type.MAIN_OPEN_WITH_TAB_CALL_LOG); 476 } else if (tab == TabIndex.CONTACTS) { 477 Logger.get(activity).logImpression(DialerImpression.Type.MAIN_OPEN_WITH_TAB_CONTACTS); 478 } else if (tab == TabIndex.VOICEMAIL) { 479 Logger.get(activity).logImpression(DialerImpression.Type.MAIN_OPEN_WITH_TAB_VOICEMAIL); 480 } else { 481 throw new IllegalStateException("Invalid tab: " + tab); 482 } 483 } 484 485 /** Returns true if the given intent is a Dial intent with data or an Add Call intent. */ isDialOrAddCallIntent(Intent intent)486 private boolean isDialOrAddCallIntent(Intent intent) { 487 if (intent == null) { 488 return false; 489 } 490 491 if (Intent.ACTION_DIAL.equals(intent.getAction())) { 492 return true; 493 } 494 495 if (Intent.ACTION_VIEW.equals(intent.getAction())) { 496 Uri data = intent.getData(); 497 if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) { 498 return true; 499 } 500 } 501 return DialpadFragment.isAddCallMode(intent); 502 } 503 504 @SuppressLint("MissingPermission") 505 @Override onActivityResume()506 public void onActivityResume() { 507 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onActivityResume"); 508 callLogFragmentListener.onActivityResume(); 509 // Start the thread that updates the smart dial database if the activity is recreated with a 510 // language change. 511 boolean forceUpdate = 512 !LocaleUtils.getLocale(activity).getISO3Language().equals(savedLanguageCode); 513 Database.get(activity).getDatabaseHelper(activity).startSmartDialUpdateThread(forceUpdate); 514 showPostCallPrompt(); 515 516 if (searchController.isInSearch() 517 || callLogAdapterOnActionModeStateChangedListener.isActionModeStateEnabled()) { 518 bottomNav.setVisibility(View.GONE); 519 } else { 520 bottomNav.setVisibility(View.VISIBLE); 521 } 522 523 /* 524 * While the activity is running, listen for the call log framework being disabled. If this is 525 * not done, user interaction with the fragment could cause call log framework state to be 526 * unexpectedly written. For example scrolling could cause the AnnotatedCallLog to be read 527 * (which would trigger database creation). 528 */ 529 LocalBroadcastManager.getInstance(activity) 530 .registerReceiver( 531 disableCallLogFrameworkReceiver, new IntentFilter("disableCallLogFramework")); 532 533 /* 534 * Similar to above, if the new call log/new voicemail is being shown and then the activity is 535 * paused, when the user returns we need to remove the NewCallLogFragment if the framework has 536 * been disabled in the meantime. 537 */ 538 bottomNavTabListener.ensureCorrectCallLogShown(); 539 bottomNavTabListener.ensureCorrectVoicemailShown(); 540 541 // Config the badge of missed calls for the new call log. 542 if (bottomNavTabListener.newCallLogFragmentActive()) { 543 if (PermissionsUtil.hasCallLogReadPermissions(activity)) { 544 missedCallCountObserver.onChange(false); // Set the initial value for the badge 545 activity 546 .getContentResolver() 547 .registerContentObserver(Calls.CONTENT_URI, true, missedCallCountObserver); 548 } else { 549 bottomNav.setNotificationCount(TabIndex.CALL_LOG, 0); 550 } 551 } 552 553 // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. 554 ThreadUtil.postDelayedOnUiThread( 555 () -> 556 MetricsComponent.get(activity) 557 .metrics() 558 .recordMemory(Metrics.OLD_MAIN_ACTIVITY_PEER_ON_RESUME_MEMORY_EVENT_NAME), 559 1000); 560 } 561 562 @Override onUserLeaveHint()563 public void onUserLeaveHint() { 564 searchController.onUserLeaveHint(); 565 } 566 567 @Override onActivityPause()568 public void onActivityPause() { 569 searchController.onActivityPause(); 570 LocalBroadcastManager.getInstance(activity).unregisterReceiver(disableCallLogFrameworkReceiver); 571 activity.getContentResolver().unregisterContentObserver(missedCallCountObserver); 572 } 573 574 @Override onActivityStop()575 public void onActivityStop() { 576 lastTabController.onActivityStop(); 577 callLogFragmentListener.onActivityStop( 578 activity.isChangingConfigurations(), 579 activity.getSystemService(KeyguardManager.class).isKeyguardLocked()); 580 } 581 582 @Override onActivityDestroyed()583 public void onActivityDestroyed() {} 584 showPostCallPrompt()585 private void showPostCallPrompt() { 586 if (TelecomUtil.isInManagedCall(activity)) { 587 // No prompt to show if the user is in a call 588 return; 589 } 590 591 if (searchController.isInSearch()) { 592 // Don't show the prompt if we're in the search ui 593 return; 594 } 595 596 PostCall.promptUserForMessageIfNecessary(activity, snackbarContainer); 597 } 598 599 @Override onSaveInstanceState(Bundle bundle)600 public void onSaveInstanceState(Bundle bundle) { 601 bundle.putString(KEY_SAVED_LANGUAGE_CODE, LocaleUtils.getLocale(activity).getISO3Language()); 602 bundle.putInt(KEY_CURRENT_TAB, bottomNav.getSelectedTab()); 603 searchController.onSaveInstanceState(bundle); 604 } 605 606 @Override onActivityResult(int requestCode, int resultCode, Intent data)607 public void onActivityResult(int requestCode, int resultCode, Intent data) { 608 LogUtil.i( 609 "OldMainActivityPeer.onActivityResult", 610 "requestCode:%d, resultCode:%d", 611 requestCode, 612 resultCode); 613 if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) { 614 searchController.onVoiceResults(resultCode, data); 615 } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_COMPOSER) { 616 if (resultCode == AppCompatActivity.RESULT_FIRST_USER) { 617 LogUtil.i( 618 "OldMainActivityPeer.onActivityResult", "returned from call composer, error occurred"); 619 String message = 620 activity.getString( 621 R.string.call_composer_connection_failed, 622 data.getStringExtra(CallComposerActivity.KEY_CONTACT_NAME)); 623 Snackbar.make(snackbarContainer, message, Snackbar.LENGTH_LONG).show(); 624 } else { 625 LogUtil.i("OldMainActivityPeer.onActivityResult", "returned from call composer, no error"); 626 } 627 628 } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_DETAILS) { 629 if (resultCode == AppCompatActivity.RESULT_OK 630 && data != null 631 && data.getBooleanExtra(OldCallDetailsActivity.EXTRA_HAS_ENRICHED_CALL_DATA, false)) { 632 String number = data.getStringExtra(OldCallDetailsActivity.EXTRA_PHONE_NUMBER); 633 int snackbarDurationMillis = 5_000; 634 Snackbar.make( 635 snackbarContainer, 636 activity.getString(R.string.ec_data_deleted), 637 snackbarDurationMillis) 638 .setAction( 639 R.string.view_conversation, 640 v -> 641 activity.startActivity( 642 IntentProvider.getSendSmsIntentProvider(number).getIntent(activity))) 643 .setActionTextColor( 644 ContextCompat.getColor(activity, R.color.dialer_snackbar_action_text_color)) 645 .show(); 646 } 647 648 } else if (requestCode == ActivityRequestCodes.DIALTACTS_DUO) { 649 // We just returned from starting Duo for a task. Reload our reachability data since it 650 // may have changed after a user finished activating Duo. 651 DuoComponent.get(activity).getDuo().reloadReachability(activity); 652 653 } else { 654 LogUtil.e("OldMainActivityPeer.onActivityResult", "Unknown request code: " + requestCode); 655 } 656 } 657 658 @Override onBackPressed()659 public boolean onBackPressed() { 660 LogUtil.enterBlock("OldMainActivityPeer.onBackPressed"); 661 if (searchController.onBackPressed()) { 662 return true; 663 } 664 return false; 665 } 666 667 @Nullable 668 @Override 669 @SuppressWarnings("unchecked") // Casts are checked using runtime methods getImpl(Class<T> callbackInterface)670 public <T> T getImpl(Class<T> callbackInterface) { 671 if (callbackInterface.isInstance(onContactSelectedListener)) { 672 return (T) onContactSelectedListener; 673 } else if (callbackInterface.isInstance(onDialpadQueryChangedListener)) { 674 return (T) onDialpadQueryChangedListener; 675 } else if (callbackInterface.isInstance(dialpadListener)) { 676 return (T) dialpadListener; 677 } else if (callbackInterface.isInstance(dialpadFragmentHostInterface)) { 678 return (T) dialpadFragmentHostInterface; 679 } else if (callbackInterface.isInstance(searchFragmentListener)) { 680 return (T) searchFragmentListener; 681 } else if (callbackInterface.isInstance(callLogAdapterOnActionModeStateChangedListener)) { 682 return (T) callLogAdapterOnActionModeStateChangedListener; 683 } else if (callbackInterface.isInstance(callLogHostInterface)) { 684 return (T) callLogHostInterface; 685 } else if (callbackInterface.isInstance(callLogFragmentListener)) { 686 return (T) callLogFragmentListener; 687 } else if (callbackInterface.isInstance(onListFragmentScrolledListener)) { 688 return (T) onListFragmentScrolledListener; 689 } else if (callbackInterface.isInstance(onPhoneNumberPickerActionListener)) { 690 return (T) onPhoneNumberPickerActionListener; 691 } else if (callbackInterface.isInstance(oldSpeedDialFragmentHost)) { 692 return (T) oldSpeedDialFragmentHost; 693 } else if (callbackInterface.isInstance(searchController)) { 694 return (T) searchController; 695 } else if (callbackInterface.isInstance(speedDialFragmentHost)) { 696 return (T) speedDialFragmentHost; 697 } else { 698 return null; 699 } 700 } 701 getNewMainSearchController( BottomNavBar bottomNavBar, FloatingActionButton fab, MainToolbar mainToolbar, View toolbarShadow, View fragmentContainer)702 public MainSearchController getNewMainSearchController( 703 BottomNavBar bottomNavBar, 704 FloatingActionButton fab, 705 MainToolbar mainToolbar, 706 View toolbarShadow, 707 View fragmentContainer) { 708 return new MainSearchController( 709 activity, bottomNavBar, fab, mainToolbar, toolbarShadow, fragmentContainer); 710 } 711 getNewOnDialpadQueryChangedListener( MainSearchController mainSearchController)712 public MainOnDialpadQueryChangedListener getNewOnDialpadQueryChangedListener( 713 MainSearchController mainSearchController) { 714 return new MainOnDialpadQueryChangedListener(mainSearchController); 715 } 716 717 /** @see OnContactSelectedListener */ 718 private static final class MainOnContactSelectedListener implements OnContactSelectedListener { 719 720 private final Context context; 721 MainOnContactSelectedListener(Context context)722 MainOnContactSelectedListener(Context context) { 723 this.context = context; 724 } 725 726 @Override onContactSelected(ImageView photo, Uri contactUri, long contactId)727 public void onContactSelected(ImageView photo, Uri contactUri, long contactId) { 728 // TODO(calderwoodra): Add impression logging 729 QuickContact.showQuickContact( 730 context, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */); 731 } 732 } 733 734 /** @see OnDialpadQueryChangedListener */ 735 protected static class MainOnDialpadQueryChangedListener 736 implements OnDialpadQueryChangedListener { 737 738 private final MainSearchController searchController; 739 MainOnDialpadQueryChangedListener(MainSearchController searchController)740 protected MainOnDialpadQueryChangedListener(MainSearchController searchController) { 741 this.searchController = searchController; 742 } 743 744 @Override onDialpadQueryChanged(String query)745 public void onDialpadQueryChanged(String query) { 746 searchController.onDialpadQueryChanged(query); 747 } 748 } 749 750 /** @see DialpadListener */ 751 private static final class MainDialpadListener implements DialpadListener { 752 753 private final MainSearchController searchController; 754 private final Context context; 755 private final UiListener<String> listener; 756 MainDialpadListener( Context context, MainSearchController searchController, UiListener<String> uiListener)757 MainDialpadListener( 758 Context context, MainSearchController searchController, UiListener<String> uiListener) { 759 this.context = context; 760 this.searchController = searchController; 761 this.listener = uiListener; 762 } 763 764 @Override getLastOutgoingCall(LastOutgoingCallCallback callback)765 public void getLastOutgoingCall(LastOutgoingCallCallback callback) { 766 ListenableFuture<String> listenableFuture = 767 DialerExecutorComponent.get(context) 768 .backgroundExecutor() 769 .submit(() -> Calls.getLastOutgoingCall(context)); 770 listener.listen(context, listenableFuture, callback::lastOutgoingCall, throwable -> {}); 771 } 772 773 @Override onDialpadShown()774 public void onDialpadShown() { 775 searchController.onDialpadShown(); 776 } 777 778 @Override onCallPlacedFromDialpad()779 public void onCallPlacedFromDialpad() { 780 // TODO(calderwoodra): logging 781 searchController.onCallPlacedFromSearch(); 782 } 783 } 784 785 /** @see SearchFragmentListener */ 786 private static final class MainSearchFragmentListener implements SearchFragmentListener { 787 788 private final MainSearchController searchController; 789 MainSearchFragmentListener(MainSearchController searchController)790 MainSearchFragmentListener(MainSearchController searchController) { 791 this.searchController = searchController; 792 } 793 794 @Override onSearchListTouch()795 public void onSearchListTouch() { 796 searchController.onSearchListTouch(); 797 } 798 799 @Override onCallPlacedFromSearch()800 public void onCallPlacedFromSearch() { 801 // TODO(calderwoodra): logging 802 searchController.onCallPlacedFromSearch(); 803 } 804 805 @Override requestingPermission()806 public void requestingPermission() { 807 searchController.requestingPermission(); 808 } 809 } 810 811 /** @see DialpadFragment.HostInterface */ 812 private static final class MainDialpadFragmentHost implements DialpadFragment.HostInterface { 813 814 @Override onDialpadSpacerTouchWithEmptyQuery()815 public boolean onDialpadSpacerTouchWithEmptyQuery() { 816 // No-op, just let the clicks fall through to the search list 817 return false; 818 } 819 820 @Override shouldShowDialpadChooser()821 public boolean shouldShowDialpadChooser() { 822 // Never show the dialpad chooser. Ever. 823 return false; 824 } 825 } 826 827 /** @see CallLogAdapter.OnActionModeStateChangedListener */ 828 private static final class MainCallLogAdapterOnActionModeStateChangedListener 829 implements CallLogAdapter.OnActionModeStateChangedListener { 830 831 private boolean isEnabled; 832 private ActionMode actionMode; 833 834 @Override onActionModeStateChanged(ActionMode actionMode, boolean isEnabled)835 public void onActionModeStateChanged(ActionMode actionMode, boolean isEnabled) { 836 this.actionMode = actionMode; 837 this.isEnabled = isEnabled; 838 } 839 840 @Override isActionModeStateEnabled()841 public boolean isActionModeStateEnabled() { 842 return isEnabled; 843 } 844 } 845 846 /** @see CallLogFragment.HostInterface */ 847 private static final class MainCallLogHost implements CallLogFragment.HostInterface { 848 849 private final FloatingActionButton fab; 850 private final MainSearchController searchController; 851 MainCallLogHost(MainSearchController searchController, FloatingActionButton fab)852 MainCallLogHost(MainSearchController searchController, FloatingActionButton fab) { 853 this.searchController = searchController; 854 this.fab = fab; 855 } 856 857 @Override showDialpad()858 public void showDialpad() { 859 searchController.showDialpad(true); 860 } 861 862 @Override enableFloatingButton(boolean enabled)863 public void enableFloatingButton(boolean enabled) { 864 LogUtil.i("MainCallLogHost.enableFloatingButton", "enabled: " + enabled); 865 if (enabled) { 866 fab.show(); 867 } else { 868 fab.hide(); 869 } 870 } 871 } 872 873 /** 874 * Handles the logic for callbacks from: 875 * 876 * <ul> 877 * <li>{@link CallLogFragment} 878 * <li>{@link CallLogQueryHandler} 879 * <li>{@link BottomNavBar} 880 * </ul> 881 * 882 * This mainly entails: 883 * 884 * <ul> 885 * <li>Handling querying for missed calls/unread voicemails. 886 * <li>Displaying a badge to the user in the bottom nav when there are missed calls/unread 887 * voicemails present. 888 * <li>Marking missed calls as read when appropriate. See {@link 889 * #markMissedCallsAsReadAndRemoveNotification()} 890 * <li>TODO(calderwoodra): multiselect 891 * </ul> 892 * 893 * @see CallLogFragmentListener 894 * @see CallLogQueryHandler.Listener 895 * @see OnBottomNavTabSelectedListener 896 */ 897 private static final class MainCallLogFragmentListener 898 implements CallLogFragmentListener, 899 CallLogQueryHandler.Listener, 900 OnBottomNavTabSelectedListener { 901 902 private final CallLogQueryHandler callLogQueryHandler; 903 private final Context context; 904 private final BottomNavBar bottomNavBar; 905 private final Toolbar toolbar; 906 private final MainBottomNavBarBottomNavTabListener bottomNavTabListener; 907 908 private @TabIndex int currentTab = TabIndex.SPEED_DIAL; 909 private long timeSelected = -1; 910 private boolean activityIsAlive; 911 912 private final ContentObserver voicemailStatusObserver = 913 new ContentObserver(new Handler()) { 914 @Override 915 public void onChange(boolean selfChange) { 916 LogUtil.i( 917 "MainCallLogFragmentListener", 918 "voicemailStatusObserver.onChange selfChange:%b", 919 selfChange); 920 super.onChange(selfChange); 921 callLogQueryHandler.fetchVoicemailStatus(); 922 } 923 }; 924 MainCallLogFragmentListener( Context context, ContentResolver contentResolver, BottomNavBar bottomNavBar, Toolbar toolbar, MainBottomNavBarBottomNavTabListener bottomNavTabListener)925 MainCallLogFragmentListener( 926 Context context, 927 ContentResolver contentResolver, 928 BottomNavBar bottomNavBar, 929 Toolbar toolbar, 930 MainBottomNavBarBottomNavTabListener bottomNavTabListener) { 931 callLogQueryHandler = new CallLogQueryHandler(context, contentResolver, this); 932 this.context = context; 933 this.bottomNavBar = bottomNavBar; 934 this.toolbar = toolbar; 935 this.bottomNavTabListener = bottomNavTabListener; 936 } 937 registerVoicemailStatusContentObserver(Context context)938 private void registerVoicemailStatusContentObserver(Context context) { 939 LogUtil.enterBlock("MainCallLogFragmentListener.registerVoicemailStatusContentObserver"); 940 if (PermissionsUtil.hasReadVoicemailPermissions(context) 941 && PermissionsUtil.hasAddVoicemailPermissions(context)) { 942 LogUtil.i("MainCallLogFragmentListener.registerVoicemailStatusContentObserver", "register"); 943 context 944 .getContentResolver() 945 .registerContentObserver( 946 VoicemailContract.Status.CONTENT_URI, true, voicemailStatusObserver); 947 } else { 948 LogUtil.w( 949 "MainCallLogFragmentListener.registerVoicemailStatusContentObserver", 950 "no voicemail read/add permissions"); 951 } 952 } 953 954 @Override updateTabUnreadCounts()955 public void updateTabUnreadCounts() { 956 callLogQueryHandler.fetchMissedCallsUnreadCount(); 957 callLogQueryHandler.fetchVoicemailUnreadCount(); 958 } 959 960 @Override showMultiSelectRemoveView(boolean show)961 public void showMultiSelectRemoveView(boolean show) { 962 bottomNavBar.setVisibility(show ? View.GONE : View.VISIBLE); 963 toolbar.setVisibility(show ? View.GONE : View.VISIBLE); 964 } 965 966 @Override onVoicemailStatusFetched(Cursor statusCursor)967 public void onVoicemailStatusFetched(Cursor statusCursor) { 968 LogUtil.i("OldMainActivityPeer.MainCallLogFragmentListener", "onVoicemailStatusFetched"); 969 VoicemailStatusCorruptionHandler.maybeFixVoicemailStatus( 970 context, statusCursor, Source.Activity); 971 972 // Update hasActiveVoicemailProvider, which controls the number of tabs displayed. 973 int numberOfActiveVoicemailSources = 974 VoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor); 975 976 boolean hasActiveVoicemailProvider = numberOfActiveVoicemailSources > 0; 977 LogUtil.i( 978 "OldMainActivityPeer.onVoicemailStatusFetched", 979 String.format( 980 Locale.US, 981 "hasActiveVoicemailProvider:%b, number of active voicemail sources:%d", 982 hasActiveVoicemailProvider, 983 numberOfActiveVoicemailSources)); 984 985 if (hasActiveVoicemailProvider) { 986 Logger.get(context).logImpression(DialerImpression.Type.MAIN_VVM_TAB_VISIBLE); 987 bottomNavBar.showVoicemail(true); 988 callLogQueryHandler.fetchVoicemailUnreadCount(); 989 } else { 990 bottomNavBar.showVoicemail(false); 991 } 992 993 StorageComponent.get(context) 994 .unencryptedSharedPrefs() 995 .edit() 996 .putBoolean( 997 VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, 998 hasActiveVoicemailProvider) 999 .apply(); 1000 1001 // TODO(uabdullah): Check if we need to force move to the VM tab (e.g in the event of 1002 // clicking a vm notification and a status wasn't yet fetched). 1003 } 1004 1005 @Override onVoicemailUnreadCountFetched(Cursor cursor)1006 public void onVoicemailUnreadCountFetched(Cursor cursor) { 1007 if (activityIsAlive) { 1008 bottomNavBar.setNotificationCount(TabIndex.VOICEMAIL, cursor.getCount()); 1009 } 1010 cursor.close(); 1011 } 1012 1013 @Override onMissedCallsUnreadCountFetched(Cursor cursor)1014 public void onMissedCallsUnreadCountFetched(Cursor cursor) { 1015 if (activityIsAlive) { 1016 bottomNavBar.setNotificationCount(TabIndex.CALL_LOG, cursor.getCount()); 1017 } 1018 cursor.close(); 1019 } 1020 1021 @Override onCallsFetched(Cursor combinedCursor)1022 public boolean onCallsFetched(Cursor combinedCursor) { 1023 // Return false; did not take ownership of cursor 1024 return false; 1025 } 1026 1027 @Override onSpeedDialSelected()1028 public void onSpeedDialSelected() { 1029 setCurrentTab(TabIndex.SPEED_DIAL); 1030 } 1031 1032 @Override onCallLogSelected()1033 public void onCallLogSelected() { 1034 setCurrentTab(TabIndex.CALL_LOG); 1035 } 1036 1037 @Override onContactsSelected()1038 public void onContactsSelected() { 1039 setCurrentTab(TabIndex.CONTACTS); 1040 } 1041 1042 @Override onVoicemailSelected()1043 public void onVoicemailSelected() { 1044 setCurrentTab(TabIndex.VOICEMAIL); 1045 } 1046 markMissedCallsAsReadAndRemoveNotification()1047 private void markMissedCallsAsReadAndRemoveNotification() { 1048 if (bottomNavTabListener.newCallLogFragmentActive()) { 1049 Futures.addCallback( 1050 CallLogComponent.get(context).getClearMissedCalls().clearAll(), 1051 new DefaultFutureCallback<>(), 1052 MoreExecutors.directExecutor()); 1053 } else { 1054 callLogQueryHandler.markMissedCallsAsRead(); 1055 CallLogNotificationsService.cancelAllMissedCalls(context); 1056 } 1057 } 1058 setCurrentTab(@abIndex int tabIndex)1059 private void setCurrentTab(@TabIndex int tabIndex) { 1060 if (currentTab == TabIndex.CALL_LOG && tabIndex != TabIndex.CALL_LOG) { 1061 markMissedCallsAsReadAndRemoveNotification(); 1062 } 1063 currentTab = tabIndex; 1064 timeSelected = System.currentTimeMillis(); 1065 } 1066 onActivityResume()1067 public void onActivityResume() { 1068 LogUtil.enterBlock("MainCallLogFragmentListener.onActivityResume"); 1069 activityIsAlive = true; 1070 registerVoicemailStatusContentObserver(context); 1071 // TODO(a bug): Don't use callLogQueryHandler 1072 callLogQueryHandler.fetchVoicemailStatus(); 1073 1074 if (!bottomNavTabListener.newCallLogFragmentActive()) { 1075 callLogQueryHandler.fetchMissedCallsUnreadCount(); 1076 } 1077 // Reset the tab on resume to restart the timer 1078 setCurrentTab(bottomNavBar.getSelectedTab()); 1079 } 1080 1081 /** Should be called when {@link AppCompatActivity#onStop()} is called. */ onActivityStop(boolean changingConfigurations, boolean keyguardLocked)1082 public void onActivityStop(boolean changingConfigurations, boolean keyguardLocked) { 1083 context.getContentResolver().unregisterContentObserver(voicemailStatusObserver); 1084 activityIsAlive = false; 1085 // The new call log fragment handles this on its own. 1086 if (!bottomNavTabListener.newCallLogFragmentActive() 1087 && viewedCallLogTabPastTimeThreshold() 1088 && !changingConfigurations 1089 && !keyguardLocked) { 1090 markMissedCallsAsReadAndRemoveNotification(); 1091 } 1092 } 1093 1094 /** 1095 * Returns true if the user has been (and still is) on the history tab for long than the 1096 * threshold. 1097 */ viewedCallLogTabPastTimeThreshold()1098 private boolean viewedCallLogTabPastTimeThreshold() { 1099 return currentTab == TabIndex.CALL_LOG 1100 && timeSelected != -1 1101 && System.currentTimeMillis() - timeSelected > TimeUnit.SECONDS.toMillis(3); 1102 } 1103 } 1104 1105 /** @see OnListFragmentScrolledListener */ 1106 private static final class MainOnListFragmentScrolledListener 1107 implements OnListFragmentScrolledListener { 1108 1109 private final View parentLayout; 1110 MainOnListFragmentScrolledListener(View parentLayout)1111 MainOnListFragmentScrolledListener(View parentLayout) { 1112 this.parentLayout = parentLayout; 1113 } 1114 1115 @Override onListFragmentScrollStateChange(int scrollState)1116 public void onListFragmentScrollStateChange(int scrollState) { 1117 DialerUtils.hideInputMethod(parentLayout); 1118 } 1119 1120 @Override onListFragmentScroll( int firstVisibleItem, int visibleItemCount, int totalItemCount)1121 public void onListFragmentScroll( 1122 int firstVisibleItem, int visibleItemCount, int totalItemCount) { 1123 // TODO: No-op for now. This should eventually show/hide the actionBar based on 1124 // interactions with the ListsFragments. 1125 } 1126 } 1127 1128 /** @see OnPhoneNumberPickerActionListener */ 1129 private static final class MainOnPhoneNumberPickerActionListener 1130 implements OnPhoneNumberPickerActionListener { 1131 1132 private final TransactionSafeActivity activity; 1133 MainOnPhoneNumberPickerActionListener(TransactionSafeActivity activity)1134 MainOnPhoneNumberPickerActionListener(TransactionSafeActivity activity) { 1135 this.activity = activity; 1136 } 1137 1138 @Override onPickDataUri( Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData)1139 public void onPickDataUri( 1140 Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData) { 1141 PhoneNumberInteraction.startInteractionForPhoneCall( 1142 activity, dataUri, isVideoCall, callSpecificAppData); 1143 } 1144 1145 @Override onPickPhoneNumber( String phoneNumber, boolean isVideoCall, CallSpecificAppData callSpecificAppData)1146 public void onPickPhoneNumber( 1147 String phoneNumber, boolean isVideoCall, CallSpecificAppData callSpecificAppData) { 1148 if (phoneNumber == null) { 1149 // Invalid phone number, but let the call go through so that InCallUI can show 1150 // an error message. 1151 phoneNumber = ""; 1152 } 1153 PreCall.start( 1154 activity, 1155 new CallIntentBuilder(phoneNumber, callSpecificAppData) 1156 .setIsVideoCall(isVideoCall) 1157 .setAllowAssistedDial(callSpecificAppData.getAllowAssistedDialing())); 1158 } 1159 1160 @Override onHomeInActionBarSelected()1161 public void onHomeInActionBarSelected() { 1162 // TODO(calderwoodra): investigate if we need to exit search here 1163 // PhoneNumberPickerFragment#onOptionsItemSelected 1164 } 1165 } 1166 1167 /** 1168 * Handles the callbacks for {@link OldSpeedDialFragment} and drag/drop logic for drag to remove. 1169 * 1170 * @see OldSpeedDialFragment.HostInterface 1171 * @see OnDragDropListener 1172 */ 1173 private static final class MainOldSpeedDialFragmentHost 1174 implements OldSpeedDialFragment.HostInterface, OnDragDropListener { 1175 1176 private final Context context; 1177 private final View rootLayout; 1178 private final BottomNavBar bottomNavBar; 1179 private final ImageView dragShadowOverlay; 1180 private final RemoveView removeView; 1181 private final View removeViewContent; 1182 private final View searchViewContainer; 1183 private final MainToolbar toolbar; 1184 MainOldSpeedDialFragmentHost( Context context, View rootLayout, BottomNavBar bottomNavBar, ImageView dragShadowOverlay, RemoveView removeView, View searchViewContainer, MainToolbar toolbar)1185 MainOldSpeedDialFragmentHost( 1186 Context context, 1187 View rootLayout, 1188 BottomNavBar bottomNavBar, 1189 ImageView dragShadowOverlay, 1190 RemoveView removeView, 1191 View searchViewContainer, 1192 MainToolbar toolbar) { 1193 this.context = context; 1194 this.rootLayout = rootLayout; 1195 this.bottomNavBar = bottomNavBar; 1196 this.dragShadowOverlay = dragShadowOverlay; 1197 this.removeView = removeView; 1198 this.searchViewContainer = searchViewContainer; 1199 this.toolbar = toolbar; 1200 removeViewContent = removeView.findViewById(R.id.remove_view_content); 1201 } 1202 1203 @Override setDragDropController(DragDropController dragDropController)1204 public void setDragDropController(DragDropController dragDropController) { 1205 removeView.setDragDropController(dragDropController); 1206 rootLayout.setOnDragListener( 1207 (v, event) -> { 1208 if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) { 1209 dragDropController.handleDragHovered(v, (int) event.getX(), (int) event.getY()); 1210 } 1211 return true; 1212 }); 1213 } 1214 1215 @Override showAllContactsTab()1216 public void showAllContactsTab() { 1217 bottomNavBar.selectTab(TabIndex.CONTACTS); 1218 Logger.get(context).logImpression(DialerImpression.Type.MAIN_OPEN_WITH_TAB_CONTACTS); 1219 } 1220 1221 @Override getDragShadowOverlay()1222 public ImageView getDragShadowOverlay() { 1223 return dragShadowOverlay; 1224 } 1225 1226 @Override setHasFrequents(boolean hasFrequents)1227 public void setHasFrequents(boolean hasFrequents) { 1228 toolbar.showClearFrequents(hasFrequents); 1229 } 1230 1231 @Override onDragStarted(int x, int y, PhoneFavoriteSquareTileView view)1232 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) { 1233 showRemoveView(true); 1234 } 1235 1236 @Override onDragHovered(int x, int y, PhoneFavoriteSquareTileView view)1237 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {} 1238 1239 @Override onDragFinished(int x, int y)1240 public void onDragFinished(int x, int y) { 1241 showRemoveView(false); 1242 } 1243 1244 @Override onDroppedOnRemove()1245 public void onDroppedOnRemove() {} 1246 showRemoveView(boolean show)1247 private void showRemoveView(boolean show) { 1248 if (show) { 1249 AnimUtils.crossFadeViews(removeViewContent, searchViewContainer, 300); 1250 } else { 1251 AnimUtils.crossFadeViews(searchViewContainer, removeViewContent, 300); 1252 } 1253 } 1254 } 1255 1256 /** 1257 * Handles the callbacks for {@link SpeedDialFragment}. 1258 * 1259 * @see SpeedDialFragment.HostInterface 1260 */ 1261 private static final class MainSpeedDialFragmentHost implements SpeedDialFragment.HostInterface { 1262 1263 private final MainToolbar toolbar; 1264 private final ViewGroup rootLayout; 1265 private final ViewGroup coordinatorLayout; 1266 private final ViewGroup fragmentContainer; 1267 MainSpeedDialFragmentHost( MainToolbar toolbar, ViewGroup rootLayout, ViewGroup coordinatorLayout, ViewGroup fragmentContainer)1268 MainSpeedDialFragmentHost( 1269 MainToolbar toolbar, 1270 ViewGroup rootLayout, 1271 ViewGroup coordinatorLayout, 1272 ViewGroup fragmentContainer) { 1273 this.toolbar = toolbar; 1274 this.rootLayout = rootLayout; 1275 this.coordinatorLayout = coordinatorLayout; 1276 this.fragmentContainer = fragmentContainer; 1277 } 1278 1279 @Override setHasFrequents(boolean hasFrequents)1280 public void setHasFrequents(boolean hasFrequents) { 1281 toolbar.showClearFrequents(hasFrequents); 1282 } 1283 1284 @Override dragFavorite(boolean start)1285 public void dragFavorite(boolean start) { 1286 rootLayout.setClipChildren(!start); 1287 coordinatorLayout.setClipChildren(!start); 1288 fragmentContainer.setClipChildren(!start); 1289 } 1290 } 1291 1292 /** 1293 * Implementation of {@link OnBottomNavTabSelectedListener} that handles logic for showing each of 1294 * the main tabs and FAB. 1295 * 1296 * <p>TODO(calderwoodra, uabdullah): Rethink the logic for showing/hiding the FAB when new 1297 * voicemail is ready. 1298 */ 1299 private static final class MainBottomNavBarBottomNavTabListener 1300 implements OnBottomNavTabSelectedListener { 1301 1302 private static final String SPEED_DIAL_TAG = "speed_dial"; 1303 private static final String CALL_LOG_TAG = "call_log"; 1304 private static final String CONTACTS_TAG = "contacts"; 1305 private static final String VOICEMAIL_TAG = "voicemail"; 1306 1307 private final TransactionSafeActivity activity; 1308 private final FragmentManager fragmentManager; 1309 private final android.support.v4.app.FragmentManager supportFragmentManager; 1310 private final FloatingActionButton fab; 1311 private final View bottomSheet; 1312 1313 @TabIndex private int selectedTab = -1; 1314 MainBottomNavBarBottomNavTabListener( TransactionSafeActivity activity, FragmentManager fragmentManager, android.support.v4.app.FragmentManager supportFragmentManager, FloatingActionButton fab, View bottomSheet)1315 private MainBottomNavBarBottomNavTabListener( 1316 TransactionSafeActivity activity, 1317 FragmentManager fragmentManager, 1318 android.support.v4.app.FragmentManager supportFragmentManager, 1319 FloatingActionButton fab, 1320 View bottomSheet) { 1321 this.activity = activity; 1322 this.fragmentManager = fragmentManager; 1323 this.supportFragmentManager = supportFragmentManager; 1324 this.fab = fab; 1325 this.bottomSheet = bottomSheet; 1326 } 1327 1328 @Override onSpeedDialSelected()1329 public void onSpeedDialSelected() { 1330 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onSpeedDialSelected"); 1331 if (selectedTab == TabIndex.SPEED_DIAL) { 1332 return; 1333 } 1334 Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_SPEED_DIAL, activity); 1335 selectedTab = TabIndex.SPEED_DIAL; 1336 1337 if (ConfigProviderComponent.get(activity) 1338 .getConfigProvider() 1339 .getBoolean("enable_new_favorites_tab", false)) { 1340 android.support.v4.app.Fragment supportFragment = 1341 supportFragmentManager.findFragmentByTag(SPEED_DIAL_TAG); 1342 showSupportFragment( 1343 supportFragment == null ? SpeedDialFragment.newInstance() : supportFragment, 1344 SPEED_DIAL_TAG); 1345 } else { 1346 Fragment fragment = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG); 1347 showFragment(fragment == null ? new OldSpeedDialFragment() : fragment, SPEED_DIAL_TAG); 1348 } 1349 fab.show(); 1350 } 1351 1352 @Override onCallLogSelected()1353 public void onCallLogSelected() { 1354 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onCallLogSelected"); 1355 if (selectedTab == TabIndex.CALL_LOG) { 1356 return; 1357 } 1358 Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_CALL_LOG, activity); 1359 selectedTab = TabIndex.CALL_LOG; 1360 1361 if (CallLogConfigComponent.get(activity).callLogConfig().isNewCallLogFragmentEnabled()) { 1362 android.support.v4.app.Fragment supportFragment = 1363 supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); 1364 showSupportFragment( 1365 supportFragment == null ? new NewCallLogFragment() : supportFragment, CALL_LOG_TAG); 1366 } else { 1367 Fragment fragment = fragmentManager.findFragmentByTag(CALL_LOG_TAG); 1368 showFragment(fragment == null ? new CallLogFragment() : fragment, CALL_LOG_TAG); 1369 } 1370 fab.show(); 1371 showPromotionBottomSheet(activity, bottomSheet); 1372 } 1373 showPromotionBottomSheet(Context context, View view)1374 private static void showPromotionBottomSheet(Context context, View view) { 1375 BottomSheetBehavior<View> bottomSheetBehavior = BottomSheetBehavior.from(view); 1376 Optional<Promotion> promotionOptional = 1377 PromotionComponent.get(context) 1378 .promotionManager() 1379 .getHighestPriorityPromotion(PromotionType.BOTTOM_SHEET); 1380 if (!promotionOptional.isPresent()) { 1381 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); 1382 return; 1383 } 1384 1385 Promotion promotion = promotionOptional.get(); 1386 ImageView icon = view.findViewById(R.id.promotion_icon); 1387 icon.setImageResource(promotion.getIconRes()); 1388 TextView details = view.findViewById(R.id.promotion_details); 1389 details.setText(promotion.getDetails()); 1390 // Required to make link clickable. 1391 details.setMovementMethod(LinkMovementMethod.getInstance()); 1392 TextView title = view.findViewById(R.id.promotion_title); 1393 title.setText(promotion.getTitle()); 1394 view.findViewById(R.id.ok_got_it) 1395 .setOnClickListener( 1396 v -> { 1397 promotion.dismiss(); 1398 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); 1399 }); 1400 view.setVisibility(View.VISIBLE); 1401 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); 1402 } 1403 disableNewCallLogFragment()1404 void disableNewCallLogFragment() { 1405 LogUtil.i("MainBottomNavBarBottomNavTabListener.disableNewCallLogFragment", "disabled"); 1406 android.support.v4.app.Fragment supportFragment = 1407 supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); 1408 if (supportFragment != null) { 1409 supportFragmentManager.beginTransaction().remove(supportFragment).commitAllowingStateLoss(); 1410 // If the NewCallLogFragment was showing, immediately show the old call log fragment 1411 // instead. 1412 if (selectedTab == TabIndex.CALL_LOG) { 1413 LogUtil.i( 1414 "MainBottomNavBarBottomNavTabListener.disableNewCallLogFragment", "showing old"); 1415 Fragment fragment = fragmentManager.findFragmentByTag(CALL_LOG_TAG); 1416 showFragment(fragment == null ? new CallLogFragment() : fragment, CALL_LOG_TAG); 1417 } 1418 } 1419 } 1420 disableNewVoicemailFragment()1421 void disableNewVoicemailFragment() { 1422 LogUtil.i("MainBottomNavBarBottomNavTabListener.disableNewVoicemailFragment", "disabled"); 1423 android.support.v4.app.Fragment supportFragment = 1424 supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1425 if (supportFragment != null) { 1426 supportFragmentManager.beginTransaction().remove(supportFragment).commitAllowingStateLoss(); 1427 // If the NewVoicemailFragment was showing, immediately show the old voicemail fragment 1428 // instead. 1429 if (selectedTab == TabIndex.VOICEMAIL) { 1430 LogUtil.i( 1431 "MainBottomNavBarBottomNavTabListener.disableNewVoicemailFragment", "showing old"); 1432 Fragment fragment = fragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1433 showFragment( 1434 fragment == null ? new VisualVoicemailCallLogFragment() : fragment, VOICEMAIL_TAG); 1435 } 1436 } 1437 } 1438 ensureCorrectCallLogShown()1439 void ensureCorrectCallLogShown() { 1440 android.support.v4.app.Fragment supportFragment = 1441 supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); 1442 if (supportFragment != null 1443 && !CallLogConfigComponent.get(activity).callLogConfig().isNewCallLogFragmentEnabled()) { 1444 LogUtil.i("MainBottomNavBarBottomNavTabListener.ensureCorrectCallLogShown", "disabling"); 1445 disableNewCallLogFragment(); 1446 } 1447 } 1448 ensureCorrectVoicemailShown()1449 void ensureCorrectVoicemailShown() { 1450 android.support.v4.app.Fragment supportFragment = 1451 supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1452 if (supportFragment != null 1453 && !CallLogConfigComponent.get(activity) 1454 .callLogConfig() 1455 .isNewVoicemailFragmentEnabled()) { 1456 LogUtil.i("MainBottomNavBarBottomNavTabListener.ensureCorrectVoicemailShown", "disabling"); 1457 disableNewVoicemailFragment(); 1458 } 1459 } 1460 newCallLogFragmentActive()1461 boolean newCallLogFragmentActive() { 1462 return supportFragmentManager.findFragmentByTag(CALL_LOG_TAG) != null 1463 || (fragmentManager.findFragmentByTag(CALL_LOG_TAG) == null 1464 && CallLogConfigComponent.get(activity) 1465 .callLogConfig() 1466 .isNewCallLogFragmentEnabled()); 1467 } 1468 newVoicemailFragmentActive()1469 boolean newVoicemailFragmentActive() { 1470 return supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null 1471 || (fragmentManager.findFragmentByTag(VOICEMAIL_TAG) == null 1472 && CallLogConfigComponent.get(activity) 1473 .callLogConfig() 1474 .isNewVoicemailFragmentEnabled()); 1475 } 1476 1477 @Override onContactsSelected()1478 public void onContactsSelected() { 1479 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onContactsSelected"); 1480 if (selectedTab == TabIndex.CONTACTS) { 1481 return; 1482 } 1483 Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_CONTACTS, activity); 1484 selectedTab = TabIndex.CONTACTS; 1485 Fragment fragment = fragmentManager.findFragmentByTag(CONTACTS_TAG); 1486 showFragment( 1487 fragment == null ? ContactsFragment.newInstance(Header.ADD_CONTACT) : fragment, 1488 CONTACTS_TAG); 1489 fab.show(); 1490 } 1491 1492 @Override onVoicemailSelected()1493 public void onVoicemailSelected() { 1494 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onVoicemailSelected"); 1495 if (selectedTab == TabIndex.VOICEMAIL) { 1496 return; 1497 } 1498 Logger.get(activity).logScreenView(ScreenEvent.Type.MAIN_VOICEMAIL, activity); 1499 selectedTab = TabIndex.VOICEMAIL; 1500 1501 if (CallLogConfigComponent.get(activity).callLogConfig().isNewVoicemailFragmentEnabled()) { 1502 android.support.v4.app.Fragment supportFragment = 1503 supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1504 showSupportFragment( 1505 supportFragment == null ? new NewVoicemailFragment() : supportFragment, VOICEMAIL_TAG); 1506 } else { 1507 VisualVoicemailCallLogFragment fragment = 1508 (VisualVoicemailCallLogFragment) fragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1509 if (fragment == null) { 1510 fragment = new VisualVoicemailCallLogFragment(); 1511 } 1512 showFragment(fragment, VOICEMAIL_TAG); 1513 fragment.setUserVisibleHint(true); 1514 fragment.onVisible(); 1515 } 1516 } 1517 showFragment(@onNull Fragment fragment, String tag)1518 private void showFragment(@NonNull Fragment fragment, String tag) { 1519 showFragment(fragment, null, tag); 1520 } 1521 1522 /** 1523 * Shows the passed in fragment and hides all of the others in one transaction. 1524 * 1525 * <p>Exactly one of fragment or supportFragment should be provided. 1526 * 1527 * <p>Executes all fragment shows/hides in one transaction with no conflicting transactions 1528 * (like showing and hiding the same fragment in the same transaction). See a bug. 1529 * 1530 * <p>Special care should be taken to avoid calling this method several times in a short window 1531 * as it can lead to fragments overlapping. 1532 */ showFragment( @ullable Fragment fragment, @Nullable android.support.v4.app.Fragment supportFragment, String tag)1533 private void showFragment( 1534 @Nullable Fragment fragment, 1535 @Nullable android.support.v4.app.Fragment supportFragment, 1536 String tag) { 1537 LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.showFragment"); 1538 Fragment oldSpeedDial = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG); 1539 Fragment oldCallLog = fragmentManager.findFragmentByTag(CALL_LOG_TAG); 1540 Fragment contacts = fragmentManager.findFragmentByTag(CONTACTS_TAG); 1541 Fragment oldVoicemail = fragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1542 1543 FragmentTransaction transaction = fragmentManager.beginTransaction(); 1544 boolean fragmentShown = showIfEqualElseHide(transaction, fragment, oldSpeedDial); 1545 fragmentShown |= showIfEqualElseHide(transaction, fragment, oldCallLog); 1546 fragmentShown |= showIfEqualElseHide(transaction, fragment, contacts); 1547 fragmentShown |= showIfEqualElseHide(transaction, fragment, oldVoicemail); 1548 1549 if (!fragmentShown && fragment != null) { 1550 LogUtil.i( 1551 "MainBottomNavBarBottomNavTabListener.showFragment", "Not added yet: " + fragment); 1552 transaction.add(R.id.fragment_container, fragment, tag); 1553 } 1554 if (activity.isSafeToCommitTransactions()) { 1555 transaction.commit(); 1556 } 1557 1558 // Handle support fragments. 1559 // TODO(calderwoodra): Handle other new fragments. 1560 android.support.v4.app.Fragment speedDial = 1561 supportFragmentManager.findFragmentByTag(SPEED_DIAL_TAG); 1562 android.support.v4.app.Fragment newCallLog = 1563 supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); 1564 android.support.v4.app.Fragment newVoicemail = 1565 supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG); 1566 1567 android.support.v4.app.FragmentTransaction supportTransaction = 1568 supportFragmentManager.beginTransaction(); 1569 boolean supportFragmentShown = 1570 showIfEqualElseHideSupport(supportTransaction, supportFragment, speedDial); 1571 supportFragmentShown |= 1572 showIfEqualElseHideSupport(supportTransaction, supportFragment, newCallLog); 1573 supportFragmentShown |= 1574 showIfEqualElseHideSupport(supportTransaction, supportFragment, newVoicemail); 1575 1576 if (!supportFragmentShown && supportFragment != null) { 1577 LogUtil.i( 1578 "MainBottomNavBarBottomNavTabListener.showFragment", 1579 "Not added yet: " + supportFragment); 1580 supportTransaction.add(R.id.fragment_container, supportFragment, tag); 1581 } 1582 if (activity.isSafeToCommitTransactions()) { 1583 supportTransaction.commit(); 1584 } 1585 } 1586 showSupportFragment( @onNull android.support.v4.app.Fragment supportFragment, String tag)1587 private void showSupportFragment( 1588 @NonNull android.support.v4.app.Fragment supportFragment, String tag) { 1589 showFragment(null, supportFragment, tag); 1590 } 1591 1592 /** 1593 * @param fragment1 will be shown if equal to {@code fragment2} 1594 * @param fragment2 will be hidden if unequal to {@code fragment1} 1595 * @return {@code true} if {@code fragment1} was shown 1596 */ showIfEqualElseHide( FragmentTransaction transaction, Fragment fragment1, Fragment fragment2)1597 private boolean showIfEqualElseHide( 1598 FragmentTransaction transaction, Fragment fragment1, Fragment fragment2) { 1599 boolean shown = false; 1600 if (fragment1 != null && fragment1.equals(fragment2)) { 1601 transaction.show(fragment1); 1602 shown = true; 1603 } else if (fragment2 != null) { 1604 if (fragment2 instanceof VisualVoicemailCallLogFragment) { 1605 fragment2.setUserVisibleHint(false); 1606 ((VisualVoicemailCallLogFragment) fragment2).onNotVisible(); 1607 } 1608 transaction.hide(fragment2); 1609 } 1610 return shown; 1611 } 1612 1613 /** 1614 * @param supportFragment1 will be shown if equal to {@code fragment2} 1615 * @param supportFragment2 will be hidden if unequal to {@code fragment1} 1616 * @return {@code true} if {@code fragment1} was shown 1617 */ showIfEqualElseHideSupport( android.support.v4.app.FragmentTransaction supportTransaction, android.support.v4.app.Fragment supportFragment1, android.support.v4.app.Fragment supportFragment2)1618 private boolean showIfEqualElseHideSupport( 1619 android.support.v4.app.FragmentTransaction supportTransaction, 1620 android.support.v4.app.Fragment supportFragment1, 1621 android.support.v4.app.Fragment supportFragment2) { 1622 boolean shown = false; 1623 if (supportFragment1 != null && supportFragment1.equals(supportFragment2)) { 1624 supportTransaction.show(supportFragment1); 1625 shown = true; 1626 } else if (supportFragment2 != null) { 1627 supportTransaction.hide(supportFragment2); 1628 } 1629 return shown; 1630 } 1631 } 1632 1633 private static final class LastTabController { 1634 1635 private final Context context; 1636 private final BottomNavBar bottomNavBar; 1637 private final boolean canShowVoicemailTab; 1638 LastTabController(Context context, BottomNavBar bottomNavBar, boolean canShowVoicemailTab)1639 LastTabController(Context context, BottomNavBar bottomNavBar, boolean canShowVoicemailTab) { 1640 this.context = context; 1641 this.bottomNavBar = bottomNavBar; 1642 this.canShowVoicemailTab = canShowVoicemailTab; 1643 } 1644 1645 /** 1646 * Get the last tab shown to the user, or the speed dial tab if this is the first time the user 1647 * has opened the app. 1648 */ 1649 @TabIndex getLastTab()1650 int getLastTab() { 1651 @TabIndex int tabIndex = TabIndex.SPEED_DIAL; 1652 1653 tabIndex = 1654 StorageComponent.get(context) 1655 .unencryptedSharedPrefs() 1656 .getInt(KEY_LAST_TAB, TabIndex.SPEED_DIAL); 1657 1658 // If the voicemail tab cannot be shown, default to showing speed dial 1659 if (tabIndex == TabIndex.VOICEMAIL && !canShowVoicemailTab) { 1660 tabIndex = TabIndex.SPEED_DIAL; 1661 } 1662 1663 return tabIndex; 1664 } 1665 onActivityStop()1666 void onActivityStop() { 1667 StorageComponent.get(context) 1668 .unencryptedSharedPrefs() 1669 .edit() 1670 .putInt(KEY_LAST_TAB, bottomNavBar.getSelectedTab()) 1671 .apply(); 1672 } 1673 } 1674 } 1675