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