• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getShowTabIntent(Context context, @TabIndex int tabIndex)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 
isShowTabIntent(Intent intent)169   static boolean isShowTabIntent(Intent intent) {
170     return ACTION_SHOW_TAB.equals(intent.getAction()) && intent.hasExtra(EXTRA_SHOW_TAB);
171   }
172 
getTabFromIntent(Intent intent)173   static @TabIndex int getTabFromIntent(Intent intent) {
174     return intent.getIntExtra(EXTRA_SHOW_TAB, -1);
175   }
176 
OldMainActivityPeer(MainActivity mainActivity)177   public OldMainActivityPeer(MainActivity mainActivity) {
178     this.mainActivity = mainActivity;
179   }
180 
181   @Override
onActivityCreate(Bundle savedInstanceState)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 
initUiListeners()190   private void initUiListeners() {
191     getLastOutgoingCallListener =
192         DialerExecutorComponent.get(mainActivity)
193             .createUiListener(mainActivity.getFragmentManager(), "Query last phone number");
194   }
195 
initLayout(Bundle savedInstanceState)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    */
canVoicemailTabBeShown(Context context)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    */
isVoicemailAvailable( Context context, PhoneAccountHandle defaultUserSelectedAccount)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 
getTelephonyManager(Context context)323   private static TelephonyManager getTelephonyManager(Context context) {
324     return context.getSystemService(TelephonyManager.class);
325   }
326 
327   @Override
onNewIntent(Intent intent)328   public void onNewIntent(Intent intent) {
329     LogUtil.enterBlock("OldMainActivityPeer.onNewIntent");
330     onHandleIntent(intent);
331   }
332 
onHandleIntent(Intent intent)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. */
isDialOrAddCallIntent(Intent 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
onActivityResume()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
onUserLeaveHint()421   public void onUserLeaveHint() {
422     searchController.onUserLeaveHint();
423   }
424 
425   @Override
onActivityPause()426   public void onActivityPause() {}
427 
428   @Override
onActivityStop()429   public void onActivityStop() {
430     lastTabController.onActivityStop();
431     callLogFragmentListener.onActivityStop(
432         mainActivity.isChangingConfigurations(),
433         mainActivity.getSystemService(KeyguardManager.class).isKeyguardLocked());
434   }
435 
436   @Override
onActivityDestroyed()437   public void onActivityDestroyed() {}
438 
showPostCallPrompt()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
onSaveInstanceState(Bundle bundle)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
onActivityResult(int requestCode, int resultCode, Intent data)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
onBackPressed()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
getImpl(Class<T> callbackInterface)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 
getNewMainSearchController( BottomNavBar bottomNavBar, FloatingActionButton fab, MainToolbar mainToolbar, View toolbarShadow)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 
getNewOnDialpadQueryChangedListener( MainSearchController mainSearchController)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 
MainOnContactSelectedListener(Context context)573     MainOnContactSelectedListener(Context context) {
574       this.context = context;
575     }
576 
577     @Override
onContactSelected(ImageView photo, Uri contactUri, long contactId)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 
MainOnDialpadQueryChangedListener(MainSearchController searchController)591     protected MainOnDialpadQueryChangedListener(MainSearchController searchController) {
592       this.searchController = searchController;
593     }
594 
595     @Override
onDialpadQueryChanged(String query)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 
MainDialpadListener( Context context, MainSearchController searchController, UiListener<String> uiListener)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
getLastOutgoingCall(LastOutgoingCallCallback callback)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
onDialpadShown()625     public void onDialpadShown() {
626       searchController.onDialpadShown();
627     }
628 
629     @Override
onCallPlacedFromDialpad()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 
MainSearchFragmentListener(MainSearchController searchController)641     MainSearchFragmentListener(MainSearchController searchController) {
642       this.searchController = searchController;
643     }
644 
645     @Override
onSearchListTouch()646     public void onSearchListTouch() {
647       searchController.onSearchListTouch();
648     }
649 
650     @Override
onCallPlacedFromSearch()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
onDialpadSpacerTouchWithEmptyQuery()661     public boolean onDialpadSpacerTouchWithEmptyQuery() {
662       // No-op, just let the clicks fall through to the search list
663       return false;
664     }
665 
666     @Override
shouldShowDialpadChooser()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
onActionModeStateChanged(boolean isEnabled)680     public void onActionModeStateChanged(boolean isEnabled) {
681       this.isEnabled = isEnabled;
682     }
683 
684     @Override
isActionModeStateEnabled()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 
MainCallLogHost(MainSearchController searchController, FloatingActionButton fab)696     MainCallLogHost(MainSearchController searchController, FloatingActionButton fab) {
697       this.searchController = searchController;
698       this.fab = fab;
699     }
700 
701     @Override
showDialpad()702     public void showDialpad() {
703       searchController.showDialpad(true);
704     }
705 
706     @Override
enableFloatingButton(boolean enabled)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 
MainCallLogFragmentListener( Context context, ContentResolver contentResolver, BottomNavBar bottomNavBar, Toolbar toolbar)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 
registerVoicemailStatusContentObserver(Context context)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
updateTabUnreadCounts()791     public void updateTabUnreadCounts() {
792       callLogQueryHandler.fetchMissedCallsUnreadCount();
793       callLogQueryHandler.fetchVoicemailUnreadCount();
794     }
795 
796     @Override
showMultiSelectRemoveView(boolean show)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
onVoicemailStatusFetched(Cursor statusCursor)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
onVoicemailUnreadCountFetched(Cursor cursor)843     public void onVoicemailUnreadCountFetched(Cursor cursor) {
844       if (activityIsAlive) {
845         bottomNavBar.setNotificationCount(TabIndex.VOICEMAIL, cursor.getCount());
846       }
847       cursor.close();
848     }
849 
850     @Override
onMissedCallsUnreadCountFetched(Cursor cursor)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
onCallsFetched(Cursor combinedCursor)859     public boolean onCallsFetched(Cursor combinedCursor) {
860       // Return false; did not take ownership of cursor
861       return false;
862     }
863 
864     @Override
onSpeedDialSelected()865     public void onSpeedDialSelected() {
866       setCurrentTab(TabIndex.SPEED_DIAL);
867     }
868 
869     @Override
onCallLogSelected()870     public void onCallLogSelected() {
871       setCurrentTab(TabIndex.CALL_LOG);
872     }
873 
874     @Override
onContactsSelected()875     public void onContactsSelected() {
876       setCurrentTab(TabIndex.CONTACTS);
877     }
878 
879     @Override
onVoicemailSelected()880     public void onVoicemailSelected() {
881       setCurrentTab(TabIndex.VOICEMAIL);
882     }
883 
markMissedCallsAsReadAndRemoveNotification()884     private void markMissedCallsAsReadAndRemoveNotification() {
885       callLogQueryHandler.markMissedCallsAsRead();
886       CallLogNotificationsService.cancelAllMissedCalls(context);
887     }
888 
setCurrentTab(@abIndex int tabIndex)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 
onActivityResume()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. */
onActivityStop(boolean changingConfigurations, boolean keyguardLocked)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      */
viewedCallLogTabPastTimeThreshold()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 
MainOnListFragmentScrolledListener(View parentLayout)932     MainOnListFragmentScrolledListener(View parentLayout) {
933       this.parentLayout = parentLayout;
934     }
935 
936     @Override
onListFragmentScrollStateChange(int scrollState)937     public void onListFragmentScrollStateChange(int scrollState) {
938       DialerUtils.hideInputMethod(parentLayout);
939     }
940 
941     @Override
onListFragmentScroll( int firstVisibleItem, int visibleItemCount, int totalItemCount)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 
MainOnPhoneNumberPickerActionListener(TransactionSafeActivity activity)955     MainOnPhoneNumberPickerActionListener(TransactionSafeActivity activity) {
956       this.activity = activity;
957     }
958 
959     @Override
onPickDataUri( Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData)960     public void onPickDataUri(
961         Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
962       PhoneNumberInteraction.startInteractionForPhoneCall(
963           activity, dataUri, isVideoCall, callSpecificAppData);
964     }
965 
966     @Override
onPickPhoneNumber( String phoneNumber, boolean isVideoCall, CallSpecificAppData callSpecificAppData)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
onHomeInActionBarSelected()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 
MainOldSpeedDialFragmentHost( BottomNavBar bottomNavBar, ImageView dragShadowOverlay, RemoveView removeView, View searchViewContainer, MainToolbar toolbar)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
setDragDropController(DragDropController dragDropController)1021     public void setDragDropController(DragDropController dragDropController) {
1022       removeView.setDragDropController(dragDropController);
1023     }
1024 
1025     @Override
showAllContactsTab()1026     public void showAllContactsTab() {
1027       bottomNavBar.selectTab(TabIndex.CONTACTS);
1028     }
1029 
1030     @Override
getDragShadowOverlay()1031     public ImageView getDragShadowOverlay() {
1032       return dragShadowOverlay;
1033     }
1034 
1035     @Override
setHasFrequents(boolean hasFrequents)1036     public void setHasFrequents(boolean hasFrequents) {
1037       toolbar.showClearFrequents(hasFrequents);
1038     }
1039 
1040     @Override
onDragStarted(int x, int y, PhoneFavoriteSquareTileView view)1041     public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
1042       showRemoveView(true);
1043     }
1044 
1045     @Override
onDragHovered(int x, int y, PhoneFavoriteSquareTileView view)1046     public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {}
1047 
1048     @Override
onDragFinished(int x, int y)1049     public void onDragFinished(int x, int y) {
1050       showRemoveView(false);
1051     }
1052 
1053     @Override
onDroppedOnRemove()1054     public void onDroppedOnRemove() {}
1055 
showRemoveView(boolean show)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 
MainBottomNavBarBottomNavTabListener( Context context, FragmentManager fragmentManager, FloatingActionButton fab)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 
preloadCallLogFragment()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
onSpeedDialSelected()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
onCallLogSelected()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
onContactsSelected()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
onVoicemailSelected()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 
hideAllFragments()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 
LastTabController(Context context, BottomNavBar bottomNavBar, boolean canShowVoicemailTab)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. */
selectLastTab()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 
onActivityStop()1248     void onActivityStop() {
1249       StorageComponent.get(context)
1250           .unencryptedSharedPrefs()
1251           .edit()
1252           .putInt(KEY_LAST_TAB, bottomNavBar.getSelectedTab())
1253           .apply();
1254     }
1255   }
1256 }
1257