Improved FAB UX.

One of the biggest issues with the FAB is that it doesn't
scale in/out properly. It looked more like setVisibility was
being called instead. This CL migrates the FAB controller to
use the built in scale in/out methods #show and #hide and the
animation is much nicer.

Some of the other issues were:
 - Now scaling animation is correct and visible.
 - No longer flashes when going in/out of search.
 - No longer shows on top of the return to call controller.
 - No longer bugs when switching between VVM TOS and contacts.
 - No longer shows FAB over VVM TOS when entering/exiting search

Bug: 62588192,35359563,64116334,27458212,37991480,67419607
Test: manual
PiperOrigin-RevId: 171608105
Change-Id: I9b3f61df35abf3659a432adf411b1b7d20eba683
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 5f3620b..164c1ea 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -38,6 +38,7 @@
 import android.support.annotation.VisibleForTesting;
 import android.support.design.widget.CoordinatorLayout;
 import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.view.ViewPager;
@@ -67,7 +68,6 @@
 import com.android.contacts.common.list.PhoneNumberListAdapter;
 import com.android.contacts.common.list.PhoneNumberPickerFragment.CursorReranker;
 import com.android.contacts.common.list.PhoneNumberPickerFragment.OnLoadFinishedListener;
-import com.android.contacts.common.widget.FloatingActionButtonController;
 import com.android.dialer.animation.AnimUtils;
 import com.android.dialer.animation.AnimationListenerAdapter;
 import com.android.dialer.app.calllog.CallLogActivity;
@@ -98,6 +98,7 @@
 import com.android.dialer.callintent.CallSpecificAppData;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
 import com.android.dialer.configprovider.ConfigProviderBindings;
 import com.android.dialer.constants.ActivityRequestCodes;
 import com.android.dialer.contactsfragment.ContactsFragment;
@@ -133,6 +134,7 @@
 import com.android.dialer.util.TouchPointManager;
 import com.android.dialer.util.TransactionSafeActivity;
 import com.android.dialer.util.ViewUtil;
+import com.android.dialer.widget.FloatingActionButtonController;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -548,7 +550,7 @@
       mDialerDatabaseHelper.startSmartDialUpdateThread();
     }
     if (mIsDialpadShown) {
-      mFloatingActionButtonController.setVisible(false);
+      mFloatingActionButtonController.scaleOut();
     } else {
       mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
     }
@@ -857,7 +859,7 @@
       mFloatingActionButtonController.scaleOut();
       maybeEnterSearchUi();
     } else {
-      mFloatingActionButtonController.setVisible(false);
+      mFloatingActionButtonController.scaleOut();
       maybeEnterSearchUi();
     }
     mActionBarController.onDialpadUp();
@@ -955,7 +957,7 @@
       ft.hide(mDialpadFragment);
       ft.commit();
     }
-    mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
+    mFloatingActionButtonController.scaleIn();
   }
 
   private void updateSearchFragmentPosition() {
@@ -1271,12 +1273,28 @@
 
     setNotInSearchUi();
 
-    // Restore the FAB for the lists fragment.
-    if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) {
-      mFloatingActionButtonController.setVisible(false);
+    // There are four states the fab can be in:
+    //   - Not visible and should remain not visible (do nothing)
+    //   - Not visible (move then show the fab)
+    //   - Visible, in the correct position (do nothing)
+    //   - Visible, in the wrong position (hide, move, then show the fab)
+    if (mFloatingActionButtonController.isVisible()
+        && getFabAlignment() != FloatingActionButtonController.ALIGN_END) {
+      mFloatingActionButtonController.scaleOut(
+          new OnVisibilityChangedListener() {
+            @Override
+            public void onHidden(FloatingActionButton floatingActionButton) {
+              super.onHidden(floatingActionButton);
+              onPageScrolled(
+                  mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */);
+              mFloatingActionButtonController.scaleIn();
+            }
+          });
+    } else if (!mFloatingActionButtonController.isVisible() && mListsFragment.shouldShowFab()) {
+      onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */);
+      ThreadUtil.getUiThreadHandler()
+          .postDelayed(() -> mFloatingActionButtonController.scaleIn(), FAB_SCALE_IN_DELAY_MS);
     }
-    mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
-    onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */);
 
     final FragmentTransaction transaction = getFragmentManager().beginTransaction();
     if (mSmartDialSearchFragment != null) {
@@ -1527,8 +1545,10 @@
   public void onPageSelected(int position) {
     updateMissedCalls();
     int tabIndex = mListsFragment.getCurrentTabIndex();
+    if (tabIndex != mPreviouslySelectedTabIndex) {
+      mFloatingActionButtonController.scaleIn();
+    }
     mPreviouslySelectedTabIndex = tabIndex;
-    mFloatingActionButtonController.setVisible(true);
     timeTabSelected = SystemClock.elapsedRealtime();
   }
 
@@ -1563,7 +1583,8 @@
     return mActionBarHeight;
   }
 
-  private int getFabAlignment() {
+  @VisibleForTesting
+  public int getFabAlignment() {
     if (!mIsLandscape
         && !isInSearchUi()
         && mListsFragment.getCurrentTabIndex() == DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL) {
diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java
index a82afa5..5cb0244 100644
--- a/java/com/android/dialer/app/calllog/CallLogFragment.java
+++ b/java/com/android/dialer/app/calllog/CallLogFragment.java
@@ -602,11 +602,14 @@
   public void onVisible() {
     LogUtil.enterBlock("CallLogFragment.onPageSelected");
     if (getActivity() != null && getActivity() instanceof HostInterface) {
-      ((HostInterface) getActivity())
-          .enableFloatingButton(mModalAlertManager == null || mModalAlertManager.isEmpty());
+      ((HostInterface) getActivity()).enableFloatingButton(!isModalAlertVisible());
     }
   }
 
+  public boolean isModalAlertVisible() {
+    return mModalAlertManager != null && !mModalAlertManager.isEmpty();
+  }
+
   @CallSuper
   public void onNotVisible() {
     LogUtil.enterBlock("CallLogFragment.onPageUnselected");
diff --git a/java/com/android/dialer/app/list/ListsFragment.java b/java/com/android/dialer/app/list/ListsFragment.java
index a94f9c1..362997a 100644
--- a/java/com/android/dialer/app/list/ListsFragment.java
+++ b/java/com/android/dialer/app/list/ListsFragment.java
@@ -35,6 +35,7 @@
 import com.android.dialer.app.R;
 import com.android.dialer.app.calllog.CallLogFragment;
 import com.android.dialer.app.calllog.CallLogNotificationsService;
+import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment;
 import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler;
 import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler.Source;
 import com.android.dialer.common.LogUtil;
@@ -409,6 +410,16 @@
     return mTabIndex;
   }
 
+  public boolean shouldShowFab() {
+    // If the VVM TOS is visible, don't show the fab
+    if (mCurrentPage instanceof VisualVoicemailCallLogFragment
+        && ((VisualVoicemailCallLogFragment) mCurrentPage).isModalAlertVisible()) {
+      return false;
+    }
+
+    return true;
+  }
+
   /**
    * External method to update unread count because the unread count changes when the user expands a
    * voicemail in the call log or when the user expands an unread call in the call history tab.
diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java
index 7dd5578..c4e6c61 100644
--- a/java/com/android/dialer/dialpadview/DialpadFragment.java
+++ b/java/com/android/dialer/dialpadview/DialpadFragment.java
@@ -69,7 +69,6 @@
 import android.widget.TextView;
 import com.android.contacts.common.dialog.CallSubjectDialog;
 import com.android.contacts.common.util.StopWatch;
-import com.android.contacts.common.widget.FloatingActionButtonController;
 import com.android.dialer.animation.AnimUtils;
 import com.android.dialer.callintent.CallInitiationType;
 import com.android.dialer.callintent.CallIntentBuilder;
@@ -79,6 +78,7 @@
 import com.android.dialer.common.concurrent.DialerExecutor;
 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
 import com.android.dialer.common.concurrent.DialerExecutors;
+import com.android.dialer.common.concurrent.ThreadUtil;
 import com.android.dialer.location.GeoUtil;
 import com.android.dialer.logging.UiAction;
 import com.android.dialer.oem.MotorolaUtils;
@@ -88,6 +88,7 @@
 import com.android.dialer.util.CallUtil;
 import com.android.dialer.util.DialerUtils;
 import com.android.dialer.util.PermissionsUtil;
+import com.android.dialer.widget.FloatingActionButtonController;
 import java.util.HashSet;
 import java.util.List;
 
@@ -1193,7 +1194,7 @@
         mOverflowPopupMenu.dismiss();
       }
 
-      mFloatingActionButtonController.setVisible(false);
+      mFloatingActionButtonController.scaleOut();
       mDialpadChooser.setVisibility(View.VISIBLE);
 
       // Instantiate the DialpadChooserAdapter and hook it up to the
@@ -1207,6 +1208,7 @@
       if (mDialpadView != null) {
         LogUtil.i("DialpadFragment.showDialpadChooser", "mDialpadView not null");
         mDialpadView.setVisibility(View.VISIBLE);
+        mFloatingActionButtonController.scaleIn();
       } else {
         LogUtil.i("DialpadFragment.showDialpadChooser", "mDialpadView null");
         mDigits.setVisibility(View.VISIBLE);
@@ -1217,7 +1219,7 @@
       if (!mFloatingActionButtonController.isVisible()) {
         // Just call 'scaleIn()' method if the mFloatingActionButtonController was not already
         // previously visible.
-        mFloatingActionButtonController.scaleIn(0);
+        mFloatingActionButtonController.scaleIn();
       }
       mDialpadChooser.setVisibility(View.GONE);
     }
@@ -1433,17 +1435,15 @@
       if (mAnimate) {
         dialpadView.animateShow();
       }
-      mFloatingActionButtonController.setVisible(false);
-      mFloatingActionButtonController.scaleIn(mAnimate ? mDialpadSlideInDuration : 0);
+      ThreadUtil.getUiThreadHandler()
+          .postDelayed(
+              () -> mFloatingActionButtonController.scaleIn(),
+              mAnimate ? mDialpadSlideInDuration : 0);
       FragmentUtils.getParentUnsafe(this, DialpadListener.class).onDialpadShown();
       mDigits.requestFocus();
     }
     if (hidden) {
-      if (mAnimate) {
-        mFloatingActionButtonController.scaleOut();
-      } else {
-        mFloatingActionButtonController.setVisible(false);
-      }
+      mFloatingActionButtonController.scaleOut();
     }
   }
 
diff --git a/java/com/android/contacts/common/widget/FloatingActionButtonController.java b/java/com/android/dialer/widget/FloatingActionButtonController.java
similarity index 80%
rename from java/com/android/contacts/common/widget/FloatingActionButtonController.java
rename to java/com/android/dialer/widget/FloatingActionButtonController.java
index d924681..a0c4e6d 100644
--- a/java/com/android/contacts/common/widget/FloatingActionButtonController.java
+++ b/java/com/android/dialer/widget/FloatingActionButtonController.java
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.contacts.common.widget;
+package com.android.dialer.widget;
 
 import android.app.Activity;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener;
 import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import com.android.contacts.common.R;
-import com.android.dialer.animation.AnimUtils;
 import com.android.dialer.common.Assert;
 
 /** Controls the movement and appearance of the FAB (Floating Action Button). */
@@ -34,10 +34,6 @@
   public static final int ALIGN_QUARTER_END = 1;
   public static final int ALIGN_END = 2;
 
-  private static final int FAB_SCALE_IN_DURATION = 266;
-  private static final int FAB_SCALE_IN_FADE_IN_DELAY = 100;
-  private static final int FAB_ICON_FADE_OUT_DURATION = 66;
-
   private final int mAnimationDuration;
   private final int mFloatingActionButtonWidth;
   private final int mFloatingActionButtonMarginRight;
@@ -67,20 +63,22 @@
     mScreenWidth = screenWidth;
   }
 
+  /** @see FloatingActionButton#isShown() */
   public boolean isVisible() {
-    return mFab.getVisibility() == View.VISIBLE;
+    return mFab.isShown();
   }
 
   /**
    * Sets FAB as shown or hidden.
    *
-   * @param visible Whether or not to make the container visible.
+   * @see #scaleIn()
+   * @see #scaleOut()
    */
   public void setVisible(boolean visible) {
     if (visible) {
-      mFab.show();
+      scaleIn();
     } else {
-      mFab.hide();
+      scaleOut();
     }
   }
 
@@ -141,27 +139,18 @@
     }
   }
 
-  /**
-   * Scales the floating action button from no height and width to its actual dimensions. This is an
-   * animation for showing the floating action button.
-   *
-   * @param delayMs The delay for the effect, in milliseconds.
-   */
-  public void scaleIn(int delayMs) {
-    setVisible(true);
-    AnimUtils.scaleIn(mFab, FAB_SCALE_IN_DURATION, delayMs);
-    AnimUtils.fadeIn(mFab, FAB_SCALE_IN_DURATION, delayMs + FAB_SCALE_IN_FADE_IN_DELAY, null);
+  /** @see FloatingActionButton#show() */
+  public void scaleIn() {
+    mFab.show();
   }
 
-  /**
-   * Scales the floating action button from its actual dimensions to no height and width. This is an
-   * animation for hiding the floating action button.
-   */
+  /** @see FloatingActionButton#hide() */
   public void scaleOut() {
-    AnimUtils.scaleOut(mFab, mAnimationDuration);
-    // Fade out the icon faster than the scale out animation, so that the icon scaling is less
-    // obvious. We don't want it to scale, but the resizing the container is not as performant.
-    AnimUtils.fadeOut(mFab, FAB_ICON_FADE_OUT_DURATION, null);
+    mFab.hide();
+  }
+
+  public void scaleOut(OnVisibilityChangedListener listener) {
+    mFab.hide(listener);
   }
 
   /**