Merge "Moving FAB logic to ContactsCommon."
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2869c62..5cf216c 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -38,7 +38,6 @@
     <!-- Match call_button_height to Phone's dimens/in_call_end_button_height -->
     <dimen name="call_button_height">74dp</dimen>
 
-    <dimen name="floating_action_button_dialpad_margin_bottom_offset">4dp</dimen>
 
     <!-- Dimensions for speed dial tiles -->
     <dimen name="contact_tile_divider_width">1dp</dimen>
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 43b2c58..53e438d 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -73,6 +73,7 @@
 import com.android.contacts.common.interactions.ImportExportDialogFragment;
 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
 import com.android.contacts.common.util.ViewUtil;
+import com.android.contacts.common.widget.FloatingActionButtonController;
 import com.android.dialer.calllog.CallLogActivity;
 import com.android.dialer.database.DialerDatabaseHelper;
 import com.android.dialer.dialpad.DialpadFragment;
@@ -90,7 +91,6 @@
 import com.android.dialer.list.SearchFragment;
 import com.android.dialer.list.SmartDialSearchFragment;
 import com.android.dialer.widget.ActionBarController;
-import com.android.dialer.widget.FloatingActionButtonController;
 import com.android.dialer.widget.SearchEditTextLayout;
 import com.android.dialer.widget.SearchEditTextLayout.OnBackButtonClickedListener;
 import com.android.dialerbind.DatabaseHelperManager;
@@ -234,7 +234,15 @@
     private DialerDatabaseHelper mDialerDatabaseHelper;
     private DragDropController mDragDropController;
     private ActionBarController mActionBarController;
+
+    private String mDescriptionDialButtonStr;
+    private String mActionMenuDialpadButtonStr;
+    private ImageButton mFloatingActionButton;
     private FloatingActionButtonController mFloatingActionButtonController;
+    /**
+     * Additional offset for FAB to be lowered when dialpad is open.
+     */
+    private int mFloatingActionButtonDialpadMarginBottomOffset;
 
     private int mActionBarHeight;
 
@@ -350,6 +358,8 @@
 
         final Resources resources = getResources();
         mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height);
+        mDescriptionDialButtonStr = resources.getString(R.string.description_dial_button);
+        mActionMenuDialpadButtonStr = resources.getString(R.string.action_menu_dialpad_button);
 
         setContentView(R.layout.dialtacts_activity);
         getWindow().setBackgroundDrawable(null);
@@ -381,13 +391,18 @@
             }
         });
 
-        boolean mIsLandscape = getResources().getConfiguration().orientation
+        mIsLandscape = getResources().getConfiguration().orientation
                 == Configuration.ORIENTATION_LANDSCAPE;
-        View floatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
-        ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
-        floatingActionButton.setOnClickListener(this);
-        mFloatingActionButtonController = new FloatingActionButtonController(this, mIsLandscape,
+        final View floatingActionButtonContainer = findViewById(
+                R.id.floating_action_button_container);
+        mFloatingActionButton = (ImageButton) findViewById(R.id.floating_action_button);
+        int floatingActionButtonWidth = resources.getDimensionPixelSize(
+                R.dimen.floating_action_button_width);
+        mFloatingActionButton.setOnClickListener(this);
+        mFloatingActionButtonController = new FloatingActionButtonController(this,
                 floatingActionButtonContainer);
+        mFloatingActionButtonDialpadMarginBottomOffset = resources.getDimensionPixelOffset(
+                R.dimen.floating_action_button_dialpad_margin_bottom_offset);
 
         ImageButton optionsMenuButton =
                 (ImageButton) searchEditTextLayout.findViewById(R.id.dialtacts_options_menu_button);
@@ -409,7 +424,6 @@
             mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH);
             mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN);
             mActionBarController.restoreInstanceState(savedInstanceState);
-            mFloatingActionButtonController.restoreInstanceState(savedInstanceState);
         }
 
         mSlideIn = AnimationUtils.loadAnimation(this,
@@ -422,13 +436,19 @@
         parentLayout = (RelativeLayout) findViewById(R.id.dialtacts_mainlayout);
         parentLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
         parentLayout.setOnDragListener(new LayoutOnDragListener());
-        parentLayout.getViewTreeObserver().addOnGlobalLayoutListener(
+        floatingActionButtonContainer.getViewTreeObserver().addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
+                        final ViewTreeObserver observer = floatingActionButtonContainer
+                                .getViewTreeObserver();
+                        if (!observer.isAlive()) {
+                            return;
+                        }
+                        observer.removeOnGlobalLayoutListener(this);
                         int screenWidth = parentLayout.getWidth();
                         mFloatingActionButtonController.setScreenWidth(screenWidth);
-                        parentLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        updateFloatingActionButtonControllerAlignment(false /* animate */);
                     }
                 });
 
@@ -468,6 +488,7 @@
         mFirstLaunch = false;
         prepareVoiceSearchButton();
         mDialerDatabaseHelper.startSmartDialUpdateThread();
+        updateFloatingActionButtonControllerAlignment(false /* animate */);
     }
 
     @Override
@@ -488,7 +509,6 @@
         outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
         outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown);
         mActionBarController.saveInstanceState(outState);
-        mFloatingActionButtonController.saveInstanceState(outState);
     }
 
     @Override
@@ -639,7 +659,9 @@
      * Callback from child DialpadFragment when the dialpad is shown.
      */
     public void onDialpadShown() {
-        mFloatingActionButtonController.updateByDialpadVisibility(true);
+        mFloatingActionButton.setImageResource(R.drawable.fab_ic_call);
+        mFloatingActionButton.setContentDescription(mDescriptionDialButtonStr);
+        updateFloatingActionButtonControllerAlignment(mDialpadFragment.getAnimate());
         if (mDialpadFragment.getAnimate()) {
             mDialpadFragment.getView().startAnimation(mSlideIn);
         } else {
@@ -668,7 +690,10 @@
         mDialpadFragment.setAnimate(animate);
 
         updateSearchFragmentPosition();
-        mFloatingActionButtonController.updateByDialpadVisibility(false);
+        mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial);
+        mFloatingActionButton.setContentDescription(mActionMenuDialpadButtonStr);
+
+        updateFloatingActionButtonControllerAlignment(animate);
         if (animate) {
             mDialpadFragment.getView().startAnimation(mSlideOut);
         } else {
@@ -1100,13 +1125,23 @@
 
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-        mFloatingActionButtonController.onPageScrolled(position, positionOffset);
+        // Only scroll the button when the first tab is selected. The button should scroll from
+        // the middle to right position only on the transition from the first tab to the second
+        // tab.
+        if (position == ListsFragment.TAB_INDEX_SPEED_DIAL) {
+            mFloatingActionButtonController.onPageScrolled(positionOffset);
+        }
     }
 
     @Override
     public void onPageSelected(int position) {
         mCurrentTabPosition = position;
-        mFloatingActionButtonController.updateByTab(position);
+        // Prevents jittery movement when clicking on tabs.
+        if (mCurrentTabPosition != ListsFragment.TAB_INDEX_SPEED_DIAL) {
+            mFloatingActionButtonController.manuallyTranslate(
+                    mFloatingActionButtonController.getTranslationXForAlignment(
+                            FloatingActionButtonController.ALIGN_RIGHT), 0);
+        }
     }
 
     @Override
@@ -1140,4 +1175,25 @@
     public void setActionBarHideOffset(int hideOffset) {
         getActionBar().setHideOffset(hideOffset);
     }
+
+    /**
+     * Updates controller based on currently known information.
+     *
+     * @param animate Whether or not to animate the transition.
+     */
+    private void updateFloatingActionButtonControllerAlignment(boolean animate) {
+        int align;
+        if (mIsDialpadShown) {
+            align = mIsLandscape ? FloatingActionButtonController.ALIGN_QUARTER_RIGHT
+                    : FloatingActionButtonController.ALIGN_MIDDLE;
+        } else {
+            align = mCurrentTabPosition == ListsFragment.TAB_INDEX_SPEED_DIAL
+                    ? FloatingActionButtonController.ALIGN_MIDDLE
+                        : FloatingActionButtonController.ALIGN_RIGHT;
+        }
+        mFloatingActionButtonController.align(align,
+                0 /* offsetX */,
+                mIsDialpadShown ? mFloatingActionButtonDialpadMarginBottomOffset : 0 /* offsetY */,
+                animate);
+    }
 }
diff --git a/src/com/android/dialer/widget/FloatingActionButtonController.java b/src/com/android/dialer/widget/FloatingActionButtonController.java
deleted file mode 100644
index 3f59153..0000000
--- a/src/com/android/dialer/widget/FloatingActionButtonController.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer.widget;
-
-import android.app.Activity;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.View;
-import android.widget.ImageButton;
-
-import com.android.contacts.common.util.ViewUtil;
-import com.android.dialer.R;
-import com.android.dialer.list.ListsFragment;
-
-/**
- * Controls the movement and appearance of the FAB.
- */
-public class FloatingActionButtonController {
-    private static final String KEY_IS_DIALPAD_VISIBLE = "key_is_dialpad_visible";
-    private static final String KEY_CURRENT_TAB_POSITION = "key_current_tab_position";
-    private static final int ANIMATION_DURATION = 250;
-
-    private int mScreenWidth;
-
-    private int mCurrentTabPosition;
-
-    private ImageButton mFloatingActionButton;
-    private View mFloatingActionButtonContainer;
-
-    private boolean mIsLandscape;
-    private boolean mIsDialpadVisible;
-    private boolean mAnimateFloatingActionButton;
-
-    private String mDescriptionDialButtonStr;
-    private String mActionMenuDialpadButtonStr;
-
-    /**
-     * Interpolator for FAB animations.
-     */
-    private Interpolator mFabInterpolator;
-
-    /**
-     * Additional offset for FAB to be lowered when dialpad is open.
-     */
-    private int mFloatingActionButtonDialpadMarginBottomOffset;
-
-    public FloatingActionButtonController(Activity activity, boolean isLandscape,
-                View container) {
-        Resources resources = activity.getResources();
-        mIsLandscape = isLandscape;
-        mFabInterpolator = AnimationUtils.loadInterpolator(activity,
-                android.R.interpolator.fast_out_slow_in);
-        mFloatingActionButtonDialpadMarginBottomOffset = resources.getDimensionPixelOffset(
-                R.dimen.floating_action_button_dialpad_margin_bottom_offset);
-        mFloatingActionButton = (ImageButton) activity.
-                findViewById(R.id.floating_action_button);
-        mDescriptionDialButtonStr = resources.getString(R.string.description_dial_button);
-        mActionMenuDialpadButtonStr = resources.getString(R.string.action_menu_dialpad_button);
-        mFloatingActionButtonContainer = container;
-        ViewUtil.setupFloatingActionButton(mFloatingActionButtonContainer, resources);
-    }
-
-    /**
-     * Passes the screen width into the class. Necessary for translation calculations.
-     *
-     * @param screenWidth the width of the screen
-     */
-    public void setScreenWidth(int screenWidth) {
-        mScreenWidth = screenWidth;
-        updateByDialpadVisibility(mIsDialpadVisible);
-    }
-
-    public void setVisible(boolean visible) {
-        mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
-    }
-
-    /**
-     * Updates the FAB location (middle to right position) as the PageView scrolls.
-     *
-     * @param position tab position to align for
-     * @param positionOffset a fraction used to calculate position of the FAB during page scroll
-     */
-    public void onPageScrolled(int position, float positionOffset) {
-        // As the page is scrolling, if we're on the first tab, update the FAB position so it
-        // moves along with it.
-        if (position == ListsFragment.TAB_INDEX_SPEED_DIAL) {
-            mFloatingActionButtonContainer.setTranslationX(
-                    (int) (positionOffset * (mScreenWidth / 2f
-                            - mFloatingActionButton.getWidth())));
-            mFloatingActionButtonContainer.setTranslationY(0);
-        }
-    }
-
-    /**
-     * Updates the FAB location given a tab position.
-     *
-     * @param position tab position to align for
-     */
-    public void updateByTab(int position) {
-        // If the screen width hasn't been set yet, don't do anything.
-        if (mScreenWidth == 0 || mIsDialpadVisible) return;
-        alignFloatingActionButtonByTab(position, false);
-        mAnimateFloatingActionButton = true;
-    }
-
-    /**
-     * Updates the FAB location to the proper location given whether or not the dialer is open.
-     *
-     * @param dialpadVisible whether or not the dialpad is currently open
-     */
-    public void updateByDialpadVisibility(boolean dialpadVisible) {
-        // If the screen width hasn't been set yet, don't do anything.
-        if (mScreenWidth == 0) return;
-        mIsDialpadVisible = dialpadVisible;
-
-        moveFloatingActionButton(mAnimateFloatingActionButton);
-        mAnimateFloatingActionButton = true;
-    }
-
-    /**
-     * Moves the FAB to the best known location given what the class currently knows.
-     *
-     * @param animate whether or not to smoothly animate the button
-     */
-    private void moveFloatingActionButton(boolean animate) {
-        if (mIsDialpadVisible) {
-            mFloatingActionButton.setImageResource(R.drawable.fab_ic_call);
-            mFloatingActionButton.setContentDescription(mDescriptionDialButtonStr);
-            alignFloatingActionButton(animate);
-        } else {
-            mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial);
-            mFloatingActionButton.setContentDescription(mActionMenuDialpadButtonStr);
-            alignFloatingActionButtonByTab(mCurrentTabPosition, mAnimateFloatingActionButton);
-        }
-    }
-
-    /**
-     * Aligns the FAB to the position for the indicated tab.
-     *
-     * @param position tab position to align for
-     * @param animate whether or not to smoothly animate the button
-     */
-    private void alignFloatingActionButtonByTab(int position, boolean animate) {
-        mCurrentTabPosition = position;
-        alignFloatingActionButton(animate);
-    }
-
-    /**
-     * Aligns the FAB to the correct position.
-     *
-     * @param animate whether or not to smoothly animate the button
-     */
-    private void alignFloatingActionButton(boolean animate) {
-        int translationX = calculateTranslationX();
-        int translationY = mIsDialpadVisible ? mFloatingActionButtonDialpadMarginBottomOffset : 0;
-        if (animate) {
-            mFloatingActionButtonContainer.animate()
-                    .translationX(translationX)
-                    .translationY(translationY)
-                    .setInterpolator(mFabInterpolator)
-                    .setDuration(ANIMATION_DURATION).start();
-        } else {
-            mFloatingActionButtonContainer.setTranslationX(translationX);
-            mFloatingActionButtonContainer.setTranslationY(translationY);
-        }
-    }
-
-    /**
-     * Calculates the translationX distance for the FAB.
-     */
-    private int calculateTranslationX() {
-        if (mIsDialpadVisible) {
-            return mIsLandscape ? mScreenWidth / 4 : 0;
-        }
-        if (mCurrentTabPosition == ListsFragment.TAB_INDEX_SPEED_DIAL) {
-            return 0;
-        }
-        return mScreenWidth / 2 - mFloatingActionButton.getWidth();
-    }
-
-    /**
-     * Saves the current state of the floating action button into a provided {@link Bundle}
-     */
-    public void saveInstanceState(Bundle outState) {
-        outState.putBoolean(KEY_IS_DIALPAD_VISIBLE, mIsDialpadVisible);
-        outState.putInt(KEY_CURRENT_TAB_POSITION, mCurrentTabPosition);
-    }
-
-    /**
-     * Restores the floating action button state from a provided {@link Bundle}
-     */
-    public void restoreInstanceState(Bundle inState) {
-        mIsDialpadVisible = inState.getBoolean(KEY_IS_DIALPAD_VISIBLE);
-        mCurrentTabPosition = inState.getInt(KEY_CURRENT_TAB_POSITION);
-    }
-}