Home | History | Annotate | Download | only in impl
      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