Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2015 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.internal.widget;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.ObjectAnimator;
     23 import android.animation.ValueAnimator;
     24 import android.content.Context;
     25 import android.content.res.TypedArray;
     26 import android.graphics.Color;
     27 import android.graphics.Point;
     28 import android.graphics.Rect;
     29 import android.graphics.Region;
     30 import android.graphics.drawable.AnimatedVectorDrawable;
     31 import android.graphics.drawable.ColorDrawable;
     32 import android.graphics.drawable.Drawable;
     33 import android.text.TextUtils;
     34 import android.util.Size;
     35 import android.util.TypedValue;
     36 import android.view.ContextThemeWrapper;
     37 import android.view.Gravity;
     38 import android.view.LayoutInflater;
     39 import android.view.Menu;
     40 import android.view.MenuItem;
     41 import android.view.MotionEvent;
     42 import android.view.View;
     43 import android.view.View.MeasureSpec;
     44 import android.view.View.OnLayoutChangeListener;
     45 import android.view.ViewConfiguration;
     46 import android.view.ViewGroup;
     47 import android.view.ViewTreeObserver;
     48 import android.view.Window;
     49 import android.view.WindowManager;
     50 import android.view.animation.Animation;
     51 import android.view.animation.AnimationSet;
     52 import android.view.animation.AnimationUtils;
     53 import android.view.animation.Interpolator;
     54 import android.view.animation.Transformation;
     55 import android.widget.ArrayAdapter;
     56 import android.widget.ImageButton;
     57 import android.widget.ImageView;
     58 import android.widget.LinearLayout;
     59 import android.widget.ListView;
     60 import android.widget.PopupWindow;
     61 import android.widget.TextView;
     62 
     63 import com.android.internal.R;
     64 import com.android.internal.util.Preconditions;
     65 
     66 import java.util.ArrayList;
     67 import java.util.LinkedList;
     68 import java.util.List;
     69 import java.util.Objects;
     70 
     71 /**
     72  * A floating toolbar for showing contextual menu items.
     73  * This view shows as many menu item buttons as can fit in the horizontal toolbar and the
     74  * the remaining menu items in a vertical overflow view when the overflow button is clicked.
     75  * The horizontal toolbar morphs into the vertical overflow view.
     76  */
     77 public final class FloatingToolbar {
     78 
     79     // This class is responsible for the public API of the floating toolbar.
     80     // It delegates rendering operations to the FloatingToolbarPopup.
     81 
     82     public static final String FLOATING_TOOLBAR_TAG = "floating_toolbar";
     83 
     84     private static final MenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER =
     85             item -> false;
     86 
     87     private final Context mContext;
     88     private final Window mWindow;
     89     private final FloatingToolbarPopup mPopup;
     90 
     91     private final Rect mContentRect = new Rect();
     92     private final Rect mPreviousContentRect = new Rect();
     93 
     94     private Menu mMenu;
     95     private List<MenuItem> mShowingMenuItems = new ArrayList<>();
     96     private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
     97 
     98     private int mSuggestedWidth;
     99     private boolean mWidthChanged = true;
    100 
    101     private final OnLayoutChangeListener mOrientationChangeHandler = new OnLayoutChangeListener() {
    102 
    103         private final Rect mNewRect = new Rect();
    104         private final Rect mOldRect = new Rect();
    105 
    106         @Override
    107         public void onLayoutChange(
    108                 View view,
    109                 int newLeft, int newRight, int newTop, int newBottom,
    110                 int oldLeft, int oldRight, int oldTop, int oldBottom) {
    111             mNewRect.set(newLeft, newRight, newTop, newBottom);
    112             mOldRect.set(oldLeft, oldRight, oldTop, oldBottom);
    113             if (mPopup.isShowing() && !mNewRect.equals(mOldRect)) {
    114                 mWidthChanged = true;
    115                 updateLayout();
    116             }
    117         }
    118     };
    119 
    120     /**
    121      * Initializes a floating toolbar.
    122      */
    123     public FloatingToolbar(Window window) {
    124         // TODO(b/65172902): Pass context in constructor when DecorView (and other callers)
    125         // supports multi-display.
    126         mContext = applyDefaultTheme(window.getContext());
    127         mWindow = Preconditions.checkNotNull(window);
    128         mPopup = new FloatingToolbarPopup(mContext, window.getDecorView());
    129     }
    130 
    131     /**
    132      * Sets the menu to be shown in this floating toolbar.
    133      * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
    134      * toolbar.
    135      */
    136     public FloatingToolbar setMenu(Menu menu) {
    137         mMenu = Preconditions.checkNotNull(menu);
    138         return this;
    139     }
    140 
    141     /**
    142      * Sets the custom listener for invocation of menu items in this floating toolbar.
    143      */
    144     public FloatingToolbar setOnMenuItemClickListener(
    145             MenuItem.OnMenuItemClickListener menuItemClickListener) {
    146         if (menuItemClickListener != null) {
    147             mMenuItemClickListener = menuItemClickListener;
    148         } else {
    149             mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
    150         }
    151         return this;
    152     }
    153 
    154     /**
    155      * Sets the content rectangle. This is the area of the interesting content that this toolbar
    156      * should avoid obstructing.
    157      * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
    158      * toolbar.
    159      */
    160     public FloatingToolbar setContentRect(Rect rect) {
    161         mContentRect.set(Preconditions.checkNotNull(rect));
    162         return this;
    163     }
    164 
    165     /**
    166      * Sets the suggested width of this floating toolbar.
    167      * The actual width will be about this size but there are no guarantees that it will be exactly
    168      * the suggested width.
    169      * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
    170      * toolbar.
    171      */
    172     public FloatingToolbar setSuggestedWidth(int suggestedWidth) {
    173         // Check if there's been a substantial width spec change.
    174         int difference = Math.abs(suggestedWidth - mSuggestedWidth);
    175         mWidthChanged = difference > (mSuggestedWidth * 0.2);
    176 
    177         mSuggestedWidth = suggestedWidth;
    178         return this;
    179     }
    180 
    181     /**
    182      * Shows this floating toolbar.
    183      */
    184     public FloatingToolbar show() {
    185         registerOrientationHandler();
    186         doShow();
    187         return this;
    188     }
    189 
    190     /**
    191      * Updates this floating toolbar to reflect recent position and view updates.
    192      * NOTE: This method is a no-op if the toolbar isn't showing.
    193      */
    194     public FloatingToolbar updateLayout() {
    195         if (mPopup.isShowing()) {
    196             doShow();
    197         }
    198         return this;
    199     }
    200 
    201     /**
    202      * Dismisses this floating toolbar.
    203      */
    204     public void dismiss() {
    205         unregisterOrientationHandler();
    206         mPopup.dismiss();
    207     }
    208 
    209     /**
    210      * Hides this floating toolbar. This is a no-op if the toolbar is not showing.
    211      * Use {@link #isHidden()} to distinguish between a hidden and a dismissed toolbar.
    212      */
    213     public void hide() {
    214         mPopup.hide();
    215     }
    216 
    217     /**
    218      * Returns {@code true} if this toolbar is currently showing. {@code false} otherwise.
    219      */
    220     public boolean isShowing() {
    221         return mPopup.isShowing();
    222     }
    223 
    224     /**
    225      * Returns {@code true} if this toolbar is currently hidden. {@code false} otherwise.
    226      */
    227     public boolean isHidden() {
    228         return mPopup.isHidden();
    229     }
    230 
    231     private void doShow() {
    232         List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
    233         tidy(menuItems);
    234         if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
    235             mPopup.dismiss();
    236             mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
    237             mShowingMenuItems = menuItems;
    238         }
    239         if (!mPopup.isShowing()) {
    240             mPopup.show(mContentRect);
    241         } else if (!mPreviousContentRect.equals(mContentRect)) {
    242             mPopup.updateCoordinates(mContentRect);
    243         }
    244         mWidthChanged = false;
    245         mPreviousContentRect.set(mContentRect);
    246     }
    247 
    248     /**
    249      * Returns true if this floating toolbar is currently showing the specified menu items.
    250      */
    251     private boolean isCurrentlyShowing(List<MenuItem> menuItems) {
    252         if (mShowingMenuItems == null || menuItems.size() != mShowingMenuItems.size()) {
    253             return false;
    254         }
    255 
    256         final int size = menuItems.size();
    257         for (int i = 0; i < size; i++) {
    258             final MenuItem menuItem = menuItems.get(i);
    259             final MenuItem showingItem = mShowingMenuItems.get(i);
    260             if (menuItem.getItemId() != showingItem.getItemId()
    261                     || !TextUtils.equals(menuItem.getTitle(), showingItem.getTitle())
    262                     || !Objects.equals(menuItem.getIcon(), showingItem.getIcon())
    263                     || menuItem.getGroupId() != showingItem.getGroupId()) {
    264                 return false;
    265             }
    266         }
    267 
    268         return true;
    269     }
    270 
    271     /**
    272      * Returns the visible and enabled menu items in the specified menu.
    273      * This method is recursive.
    274      */
    275     private List<MenuItem> getVisibleAndEnabledMenuItems(Menu menu) {
    276         List<MenuItem> menuItems = new ArrayList<>();
    277         for (int i = 0; (menu != null) && (i < menu.size()); i++) {
    278             MenuItem menuItem = menu.getItem(i);
    279             if (menuItem.isVisible() && menuItem.isEnabled()) {
    280                 Menu subMenu = menuItem.getSubMenu();
    281                 if (subMenu != null) {
    282                     menuItems.addAll(getVisibleAndEnabledMenuItems(subMenu));
    283                 } else {
    284                     menuItems.add(menuItem);
    285                 }
    286             }
    287         }
    288         return menuItems;
    289     }
    290 
    291     /**
    292      * Update the list of menu items to conform to certain requirements.
    293      */
    294     private void tidy(List<MenuItem> menuItems) {
    295         int assistItemIndex = -1;
    296         Drawable assistItemDrawable = null;
    297 
    298         final int size = menuItems.size();
    299         for (int i = 0; i < size; i++) {
    300             final MenuItem menuItem = menuItems.get(i);
    301 
    302             if (menuItem.getItemId() == android.R.id.textAssist) {
    303                 assistItemIndex = i;
    304                 assistItemDrawable = menuItem.getIcon();
    305             }
    306 
    307             // Remove icons for all menu items with text.
    308             if (!TextUtils.isEmpty(menuItem.getTitle())) {
    309                 menuItem.setIcon(null);
    310             }
    311         }
    312         if (assistItemIndex > -1) {
    313             final MenuItem assistMenuItem = menuItems.remove(assistItemIndex);
    314             // Ensure the assist menu item preserves its icon.
    315             assistMenuItem.setIcon(assistItemDrawable);
    316             // Ensure the assist menu item is always the first item.
    317             menuItems.add(0, assistMenuItem);
    318         }
    319     }
    320 
    321     private void registerOrientationHandler() {
    322         unregisterOrientationHandler();
    323         mWindow.getDecorView().addOnLayoutChangeListener(mOrientationChangeHandler);
    324     }
    325 
    326     private void unregisterOrientationHandler() {
    327         mWindow.getDecorView().removeOnLayoutChangeListener(mOrientationChangeHandler);
    328     }
    329 
    330 
    331     /**
    332      * A popup window used by the floating toolbar.
    333      *
    334      * This class is responsible for the rendering/animation of the floating toolbar.
    335      * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
    336      * to transition between panels.
    337      */
    338     private static final class FloatingToolbarPopup {
    339 
    340         /* Minimum and maximum number of items allowed in the overflow. */
    341         private static final int MIN_OVERFLOW_SIZE = 2;
    342         private static final int MAX_OVERFLOW_SIZE = 4;
    343 
    344         private final Context mContext;
    345         private final View mParent;  // Parent for the popup window.
    346         private final PopupWindow mPopupWindow;
    347 
    348         /* Margins between the popup window and it's content. */
    349         private final int mMarginHorizontal;
    350         private final int mMarginVertical;
    351 
    352         /* View components */
    353         private final ViewGroup mContentContainer;  // holds all contents.
    354         private final ViewGroup mMainPanel;  // holds menu items that are initially displayed.
    355         private final OverflowPanel mOverflowPanel;  // holds menu items hidden in the overflow.
    356         private final ImageButton mOverflowButton;  // opens/closes the overflow.
    357         /* overflow button drawables. */
    358         private final Drawable mArrow;
    359         private final Drawable mOverflow;
    360         private final AnimatedVectorDrawable mToArrow;
    361         private final AnimatedVectorDrawable mToOverflow;
    362 
    363         private final OverflowPanelViewHelper mOverflowPanelViewHelper;
    364 
    365         /* Animation interpolators. */
    366         private final Interpolator mLogAccelerateInterpolator;
    367         private final Interpolator mFastOutSlowInInterpolator;
    368         private final Interpolator mLinearOutSlowInInterpolator;
    369         private final Interpolator mFastOutLinearInInterpolator;
    370 
    371         /* Animations. */
    372         private final AnimatorSet mShowAnimation;
    373         private final AnimatorSet mDismissAnimation;
    374         private final AnimatorSet mHideAnimation;
    375         private final AnimationSet mOpenOverflowAnimation;
    376         private final AnimationSet mCloseOverflowAnimation;
    377         private final Animation.AnimationListener mOverflowAnimationListener;
    378 
    379         private final Rect mViewPortOnScreen = new Rect();  // portion of screen we can draw in.
    380         private final Point mCoordsOnWindow = new Point();  // popup window coordinates.
    381         /* Temporary data holders. Reset values before using. */
    382         private final int[] mTmpCoords = new int[2];
    383 
    384         private final Region mTouchableRegion = new Region();
    385         private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
    386                 info -> {
    387                     info.contentInsets.setEmpty();
    388                     info.visibleInsets.setEmpty();
    389                     info.touchableRegion.set(mTouchableRegion);
    390                     info.setTouchableInsets(
    391                             ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
    392                 };
    393 
    394         private final int mLineHeight;
    395         private final int mIconTextSpacing;
    396 
    397         /**
    398          * @see OverflowPanelViewHelper#preparePopupContent().
    399          */
    400         private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
    401             @Override
    402             public void run() {
    403                 setPanelsStatesAtRestingPosition();
    404                 setContentAreaAsTouchableSurface();
    405                 mContentContainer.setAlpha(1);
    406             }
    407         };
    408 
    409         private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing.
    410         private boolean mHidden; // tracks whether this popup is hidden or hiding.
    411 
    412         /* Calculated sizes for panels and overflow button. */
    413         private final Size mOverflowButtonSize;
    414         private Size mOverflowPanelSize;  // Should be null when there is no overflow.
    415         private Size mMainPanelSize;
    416 
    417         /* Item click listeners */
    418         private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
    419         private final View.OnClickListener mMenuItemButtonOnClickListener =
    420                 new View.OnClickListener() {
    421                     @Override
    422                     public void onClick(View v) {
    423                         if (v.getTag() instanceof MenuItem) {
    424                             if (mOnMenuItemClickListener != null) {
    425                                 mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
    426                             }
    427                         }
    428                     }
    429                 };
    430 
    431         private boolean mOpenOverflowUpwards;  // Whether the overflow opens upwards or downwards.
    432         private boolean mIsOverflowOpen;
    433 
    434         private int mTransitionDurationScale;  // Used to scale the toolbar transition duration.
    435 
    436         /**
    437          * Initializes a new floating toolbar popup.
    438          *
    439          * @param parent  A parent view to get the {@link android.view.View#getWindowToken()} token
    440          *      from.
    441          */
    442         public FloatingToolbarPopup(Context context, View parent) {
    443             mParent = Preconditions.checkNotNull(parent);
    444             mContext = Preconditions.checkNotNull(context);
    445             mContentContainer = createContentContainer(context);
    446             mPopupWindow = createPopupWindow(mContentContainer);
    447             mMarginHorizontal = parent.getResources()
    448                     .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
    449             mMarginVertical = parent.getResources()
    450                     .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
    451             mLineHeight = context.getResources()
    452                     .getDimensionPixelSize(R.dimen.floating_toolbar_height);
    453             mIconTextSpacing = context.getResources()
    454                     .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_side_padding);
    455 
    456             // Interpolators
    457             mLogAccelerateInterpolator = new LogAccelerateInterpolator();
    458             mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
    459                     mContext, android.R.interpolator.fast_out_slow_in);
    460             mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
    461                     mContext, android.R.interpolator.linear_out_slow_in);
    462             mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
    463                     mContext, android.R.interpolator.fast_out_linear_in);
    464 
    465             // Drawables. Needed for views.
    466             mArrow = mContext.getResources()
    467                     .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme());
    468             mArrow.setAutoMirrored(true);
    469             mOverflow = mContext.getResources()
    470                     .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme());
    471             mOverflow.setAutoMirrored(true);
    472             mToArrow = (AnimatedVectorDrawable) mContext.getResources()
    473                     .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme());
    474             mToArrow.setAutoMirrored(true);
    475             mToOverflow = (AnimatedVectorDrawable) mContext.getResources()
    476                     .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme());
    477             mToOverflow.setAutoMirrored(true);
    478 
    479             // Views
    480             mOverflowButton = createOverflowButton();
    481             mOverflowButtonSize = measure(mOverflowButton);
    482             mMainPanel = createMainPanel();
    483             mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext);
    484             mOverflowPanel = createOverflowPanel();
    485 
    486             // Animation. Need views.
    487             mOverflowAnimationListener = createOverflowAnimationListener();
    488             mOpenOverflowAnimation = new AnimationSet(true);
    489             mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
    490             mCloseOverflowAnimation = new AnimationSet(true);
    491             mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
    492             mShowAnimation = createEnterAnimation(mContentContainer);
    493             mDismissAnimation = createExitAnimation(
    494                     mContentContainer,
    495                     150,  // startDelay
    496                     new AnimatorListenerAdapter() {
    497                         @Override
    498                         public void onAnimationEnd(Animator animation) {
    499                             mPopupWindow.dismiss();
    500                             mContentContainer.removeAllViews();
    501                         }
    502                     });
    503             mHideAnimation = createExitAnimation(
    504                     mContentContainer,
    505                     0,  // startDelay
    506                     new AnimatorListenerAdapter() {
    507                         @Override
    508                         public void onAnimationEnd(Animator animation) {
    509                             mPopupWindow.dismiss();
    510                         }
    511                     });
    512         }
    513 
    514         /**
    515          * Lays out buttons for the specified menu items.
    516          * Requires a subsequent call to {@link #show()} to show the items.
    517          */
    518         public void layoutMenuItems(
    519                 List<MenuItem> menuItems,
    520                 MenuItem.OnMenuItemClickListener menuItemClickListener,
    521                 int suggestedWidth) {
    522             mOnMenuItemClickListener = menuItemClickListener;
    523             cancelOverflowAnimations();
    524             clearPanels();
    525             menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
    526             if (!menuItems.isEmpty()) {
    527                 // Add remaining items to the overflow.
    528                 layoutOverflowPanelItems(menuItems);
    529             }
    530             updatePopupSize();
    531         }
    532 
    533         /**
    534          * Shows this popup at the specified coordinates.
    535          * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
    536          */
    537         public void show(Rect contentRectOnScreen) {
    538             Preconditions.checkNotNull(contentRectOnScreen);
    539 
    540             if (isShowing()) {
    541                 return;
    542             }
    543 
    544             mHidden = false;
    545             mDismissed = false;
    546             cancelDismissAndHideAnimations();
    547             cancelOverflowAnimations();
    548 
    549             refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
    550             preparePopupContent();
    551             // We need to specify the position in window coordinates.
    552             // TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can
    553             // specify the popup position in screen coordinates.
    554             mPopupWindow.showAtLocation(
    555                     mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y);
    556             setTouchableSurfaceInsetsComputer();
    557             runShowAnimation();
    558         }
    559 
    560         /**
    561          * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op.
    562          */
    563         public void dismiss() {
    564             if (mDismissed) {
    565                 return;
    566             }
    567 
    568             mHidden = false;
    569             mDismissed = true;
    570             mHideAnimation.cancel();
    571 
    572             runDismissAnimation();
    573             setZeroTouchableSurface();
    574         }
    575 
    576         /**
    577          * Hides this popup. This is a no-op if this popup is not showing.
    578          * Use {@link #isHidden()} to distinguish between a hidden and a dismissed popup.
    579          */
    580         public void hide() {
    581             if (!isShowing()) {
    582                 return;
    583             }
    584 
    585             mHidden = true;
    586             runHideAnimation();
    587             setZeroTouchableSurface();
    588         }
    589 
    590         /**
    591          * Returns {@code true} if this popup is currently showing. {@code false} otherwise.
    592          */
    593         public boolean isShowing() {
    594             return !mDismissed && !mHidden;
    595         }
    596 
    597         /**
    598          * Returns {@code true} if this popup is currently hidden. {@code false} otherwise.
    599          */
    600         public boolean isHidden() {
    601             return mHidden;
    602         }
    603 
    604         /**
    605          * Updates the coordinates of this popup.
    606          * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
    607          * This is a no-op if this popup is not showing.
    608          */
    609         public void updateCoordinates(Rect contentRectOnScreen) {
    610             Preconditions.checkNotNull(contentRectOnScreen);
    611 
    612             if (!isShowing() || !mPopupWindow.isShowing()) {
    613                 return;
    614             }
    615 
    616             cancelOverflowAnimations();
    617             refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
    618             preparePopupContent();
    619             // We need to specify the position in window coordinates.
    620             // TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can
    621             // specify the popup position in screen coordinates.
    622             mPopupWindow.update(
    623                     mCoordsOnWindow.x, mCoordsOnWindow.y,
    624                     mPopupWindow.getWidth(), mPopupWindow.getHeight());
    625         }
    626 
    627         private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
    628             refreshViewPort();
    629 
    630             // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
    631             // landscape.
    632             final int x = Math.min(
    633                     contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
    634                     mViewPortOnScreen.right - mPopupWindow.getWidth());
    635 
    636             final int y;
    637 
    638             final int availableHeightAboveContent =
    639                     contentRectOnScreen.top - mViewPortOnScreen.top;
    640             final int availableHeightBelowContent =
    641                     mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
    642 
    643             final int margin = 2 * mMarginVertical;
    644             final int toolbarHeightWithVerticalMargin = mLineHeight + margin;
    645 
    646             if (!hasOverflow()) {
    647                 if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) {
    648                     // There is enough space at the top of the content.
    649                     y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
    650                 } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) {
    651                     // There is enough space at the bottom of the content.
    652                     y = contentRectOnScreen.bottom;
    653                 } else if (availableHeightBelowContent >= mLineHeight) {
    654                     // Just enough space to fit the toolbar with no vertical margins.
    655                     y = contentRectOnScreen.bottom - mMarginVertical;
    656                 } else {
    657                     // Not enough space. Prefer to position as high as possible.
    658                     y = Math.max(
    659                             mViewPortOnScreen.top,
    660                             contentRectOnScreen.top - toolbarHeightWithVerticalMargin);
    661                 }
    662             } else {
    663                 // Has an overflow.
    664                 final int minimumOverflowHeightWithMargin =
    665                         calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin;
    666                 final int availableHeightThroughContentDown = mViewPortOnScreen.bottom -
    667                         contentRectOnScreen.top + toolbarHeightWithVerticalMargin;
    668                 final int availableHeightThroughContentUp = contentRectOnScreen.bottom -
    669                         mViewPortOnScreen.top + toolbarHeightWithVerticalMargin;
    670 
    671                 if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) {
    672                     // There is enough space at the top of the content rect for the overflow.
    673                     // Position above and open upwards.
    674                     updateOverflowHeight(availableHeightAboveContent - margin);
    675                     y = contentRectOnScreen.top - mPopupWindow.getHeight();
    676                     mOpenOverflowUpwards = true;
    677                 } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
    678                         && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
    679                     // There is enough space at the top of the content rect for the main panel
    680                     // but not the overflow.
    681                     // Position above but open downwards.
    682                     updateOverflowHeight(availableHeightThroughContentDown - margin);
    683                     y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
    684                     mOpenOverflowUpwards = false;
    685                 } else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) {
    686                     // There is enough space at the bottom of the content rect for the overflow.
    687                     // Position below and open downwards.
    688                     updateOverflowHeight(availableHeightBelowContent - margin);
    689                     y = contentRectOnScreen.bottom;
    690                     mOpenOverflowUpwards = false;
    691                 } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin
    692                         && mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) {
    693                     // There is enough space at the bottom of the content rect for the main panel
    694                     // but not the overflow.
    695                     // Position below but open upwards.
    696                     updateOverflowHeight(availableHeightThroughContentUp - margin);
    697                     y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin -
    698                             mPopupWindow.getHeight();
    699                     mOpenOverflowUpwards = true;
    700                 } else {
    701                     // Not enough space.
    702                     // Position at the top of the view port and open downwards.
    703                     updateOverflowHeight(mViewPortOnScreen.height() - margin);
    704                     y = mViewPortOnScreen.top;
    705                     mOpenOverflowUpwards = false;
    706                 }
    707             }
    708 
    709             // We later specify the location of PopupWindow relative to the attached window.
    710             // The idea here is that 1) we can get the location of a View in both window coordinates
    711             // and screen coordiantes, where the offset between them should be equal to the window
    712             // origin, and 2) we can use an arbitrary for this calculation while calculating the
    713             // location of the rootview is supposed to be least expensive.
    714             // TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can avoid
    715             // the following calculation.
    716             mParent.getRootView().getLocationOnScreen(mTmpCoords);
    717             int rootViewLeftOnScreen = mTmpCoords[0];
    718             int rootViewTopOnScreen = mTmpCoords[1];
    719             mParent.getRootView().getLocationInWindow(mTmpCoords);
    720             int rootViewLeftOnWindow = mTmpCoords[0];
    721             int rootViewTopOnWindow = mTmpCoords[1];
    722             int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow;
    723             int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow;
    724             mCoordsOnWindow.set(
    725                     Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
    726         }
    727 
    728         /**
    729          * Performs the "show" animation on the floating popup.
    730          */
    731         private void runShowAnimation() {
    732             mShowAnimation.start();
    733         }
    734 
    735         /**
    736          * Performs the "dismiss" animation on the floating popup.
    737          */
    738         private void runDismissAnimation() {
    739             mDismissAnimation.start();
    740         }
    741 
    742         /**
    743          * Performs the "hide" animation on the floating popup.
    744          */
    745         private void runHideAnimation() {
    746             mHideAnimation.start();
    747         }
    748 
    749         private void cancelDismissAndHideAnimations() {
    750             mDismissAnimation.cancel();
    751             mHideAnimation.cancel();
    752         }
    753 
    754         private void cancelOverflowAnimations() {
    755             mContentContainer.clearAnimation();
    756             mMainPanel.animate().cancel();
    757             mOverflowPanel.animate().cancel();
    758             mToArrow.stop();
    759             mToOverflow.stop();
    760         }
    761 
    762         private void openOverflow() {
    763             final int targetWidth = mOverflowPanelSize.getWidth();
    764             final int targetHeight = mOverflowPanelSize.getHeight();
    765             final int startWidth = mContentContainer.getWidth();
    766             final int startHeight = mContentContainer.getHeight();
    767             final float startY = mContentContainer.getY();
    768             final float left = mContentContainer.getX();
    769             final float right = left + mContentContainer.getWidth();
    770             Animation widthAnimation = new Animation() {
    771                 @Override
    772                 protected void applyTransformation(float interpolatedTime, Transformation t) {
    773                     int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
    774                     setWidth(mContentContainer, startWidth + deltaWidth);
    775                     if (isInRTLMode()) {
    776                         mContentContainer.setX(left);
    777 
    778                         // Lock the panels in place.
    779                         mMainPanel.setX(0);
    780                         mOverflowPanel.setX(0);
    781                     } else {
    782                         mContentContainer.setX(right - mContentContainer.getWidth());
    783 
    784                         // Offset the panels' positions so they look like they're locked in place
    785                         // on the screen.
    786                         mMainPanel.setX(mContentContainer.getWidth() - startWidth);
    787                         mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth);
    788                     }
    789                 }
    790             };
    791             Animation heightAnimation = new Animation() {
    792                 @Override
    793                 protected void applyTransformation(float interpolatedTime, Transformation t) {
    794                     int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
    795                     setHeight(mContentContainer, startHeight + deltaHeight);
    796                     if (mOpenOverflowUpwards) {
    797                         mContentContainer.setY(
    798                                 startY - (mContentContainer.getHeight() - startHeight));
    799                         positionContentYCoordinatesIfOpeningOverflowUpwards();
    800                     }
    801                 }
    802             };
    803             final float overflowButtonStartX = mOverflowButton.getX();
    804             final float overflowButtonTargetX = isInRTLMode() ?
    805                     overflowButtonStartX + targetWidth - mOverflowButton.getWidth() :
    806                     overflowButtonStartX - targetWidth + mOverflowButton.getWidth();
    807             Animation overflowButtonAnimation = new Animation() {
    808                 @Override
    809                 protected void applyTransformation(float interpolatedTime, Transformation t) {
    810                     float overflowButtonX = overflowButtonStartX
    811                             + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
    812                     float deltaContainerWidth = isInRTLMode() ?
    813                             0 :
    814                             mContentContainer.getWidth() - startWidth;
    815                     float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
    816                     mOverflowButton.setX(actualOverflowButtonX);
    817                 }
    818             };
    819             widthAnimation.setInterpolator(mLogAccelerateInterpolator);
    820             widthAnimation.setDuration(getAdjustedDuration(250));
    821             heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
    822             heightAnimation.setDuration(getAdjustedDuration(250));
    823             overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
    824             overflowButtonAnimation.setDuration(getAdjustedDuration(250));
    825             mOpenOverflowAnimation.getAnimations().clear();
    826             mOpenOverflowAnimation.getAnimations().clear();
    827             mOpenOverflowAnimation.addAnimation(widthAnimation);
    828             mOpenOverflowAnimation.addAnimation(heightAnimation);
    829             mOpenOverflowAnimation.addAnimation(overflowButtonAnimation);
    830             mContentContainer.startAnimation(mOpenOverflowAnimation);
    831             mIsOverflowOpen = true;
    832             mMainPanel.animate()
    833                     .alpha(0).withLayer()
    834                     .setInterpolator(mLinearOutSlowInInterpolator)
    835                     .setDuration(250)
    836                     .start();
    837             mOverflowPanel.setAlpha(1); // fadeIn in 0ms.
    838         }
    839 
    840         private void closeOverflow() {
    841             final int targetWidth = mMainPanelSize.getWidth();
    842             final int startWidth = mContentContainer.getWidth();
    843             final float left = mContentContainer.getX();
    844             final float right = left + mContentContainer.getWidth();
    845             Animation widthAnimation = new Animation() {
    846                 @Override
    847                 protected void applyTransformation(float interpolatedTime, Transformation t) {
    848                     int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
    849                     setWidth(mContentContainer, startWidth + deltaWidth);
    850                     if (isInRTLMode()) {
    851                         mContentContainer.setX(left);
    852 
    853                         // Lock the panels in place.
    854                         mMainPanel.setX(0);
    855                         mOverflowPanel.setX(0);
    856                     } else {
    857                         mContentContainer.setX(right - mContentContainer.getWidth());
    858 
    859                         // Offset the panels' positions so they look like they're locked in place
    860                         // on the screen.
    861                         mMainPanel.setX(mContentContainer.getWidth() - targetWidth);
    862                         mOverflowPanel.setX(mContentContainer.getWidth() - startWidth);
    863                     }
    864                 }
    865             };
    866             final int targetHeight = mMainPanelSize.getHeight();
    867             final int startHeight = mContentContainer.getHeight();
    868             final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
    869             Animation heightAnimation = new Animation() {
    870                 @Override
    871                 protected void applyTransformation(float interpolatedTime, Transformation t) {
    872                     int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
    873                     setHeight(mContentContainer, startHeight + deltaHeight);
    874                     if (mOpenOverflowUpwards) {
    875                         mContentContainer.setY(bottom - mContentContainer.getHeight());
    876                         positionContentYCoordinatesIfOpeningOverflowUpwards();
    877                     }
    878                 }
    879             };
    880             final float overflowButtonStartX = mOverflowButton.getX();
    881             final float overflowButtonTargetX = isInRTLMode() ?
    882                     overflowButtonStartX - startWidth + mOverflowButton.getWidth() :
    883                     overflowButtonStartX + startWidth - mOverflowButton.getWidth();
    884             Animation overflowButtonAnimation = new Animation() {
    885                 @Override
    886                 protected void applyTransformation(float interpolatedTime, Transformation t) {
    887                     float overflowButtonX = overflowButtonStartX
    888                             + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
    889                     float deltaContainerWidth = isInRTLMode() ?
    890                             0 :
    891                             mContentContainer.getWidth() - startWidth;
    892                     float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
    893                     mOverflowButton.setX(actualOverflowButtonX);
    894                 }
    895             };
    896             widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
    897             widthAnimation.setDuration(getAdjustedDuration(250));
    898             heightAnimation.setInterpolator(mLogAccelerateInterpolator);
    899             heightAnimation.setDuration(getAdjustedDuration(250));
    900             overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
    901             overflowButtonAnimation.setDuration(getAdjustedDuration(250));
    902             mCloseOverflowAnimation.getAnimations().clear();
    903             mCloseOverflowAnimation.addAnimation(widthAnimation);
    904             mCloseOverflowAnimation.addAnimation(heightAnimation);
    905             mCloseOverflowAnimation.addAnimation(overflowButtonAnimation);
    906             mContentContainer.startAnimation(mCloseOverflowAnimation);
    907             mIsOverflowOpen = false;
    908             mMainPanel.animate()
    909                     .alpha(1).withLayer()
    910                     .setInterpolator(mFastOutLinearInInterpolator)
    911                     .setDuration(100)
    912                     .start();
    913             mOverflowPanel.animate()
    914                     .alpha(0).withLayer()
    915                     .setInterpolator(mLinearOutSlowInInterpolator)
    916                     .setDuration(150)
    917                     .start();
    918         }
    919 
    920         /**
    921          * Defines the position of the floating toolbar popup panels when transition animation has
    922          * stopped.
    923          */
    924         private void setPanelsStatesAtRestingPosition() {
    925             mOverflowButton.setEnabled(true);
    926             mOverflowPanel.awakenScrollBars();
    927 
    928             if (mIsOverflowOpen) {
    929                 // Set open state.
    930                 final Size containerSize = mOverflowPanelSize;
    931                 setSize(mContentContainer, containerSize);
    932                 mMainPanel.setAlpha(0);
    933                 mMainPanel.setVisibility(View.INVISIBLE);
    934                 mOverflowPanel.setAlpha(1);
    935                 mOverflowPanel.setVisibility(View.VISIBLE);
    936                 mOverflowButton.setImageDrawable(mArrow);
    937                 mOverflowButton.setContentDescription(mContext.getString(
    938                         R.string.floating_toolbar_close_overflow_description));
    939 
    940                 // Update x-coordinates depending on RTL state.
    941                 if (isInRTLMode()) {
    942                     mContentContainer.setX(mMarginHorizontal);  // align left
    943                     mMainPanel.setX(0);  // align left
    944                     mOverflowButton.setX(  // align right
    945                             containerSize.getWidth() - mOverflowButtonSize.getWidth());
    946                     mOverflowPanel.setX(0);  // align left
    947                 } else {
    948                     mContentContainer.setX(  // align right
    949                             mPopupWindow.getWidth() -
    950                                     containerSize.getWidth() - mMarginHorizontal);
    951                     mMainPanel.setX(-mContentContainer.getX());  // align right
    952                     mOverflowButton.setX(0);  // align left
    953                     mOverflowPanel.setX(0);  // align left
    954                 }
    955 
    956                 // Update y-coordinates depending on overflow's open direction.
    957                 if (mOpenOverflowUpwards) {
    958                     mContentContainer.setY(mMarginVertical);  // align top
    959                     mMainPanel.setY(  // align bottom
    960                             containerSize.getHeight() - mContentContainer.getHeight());
    961                     mOverflowButton.setY(  // align bottom
    962                             containerSize.getHeight() - mOverflowButtonSize.getHeight());
    963                     mOverflowPanel.setY(0);  // align top
    964                 } else {
    965                     // opens downwards.
    966                     mContentContainer.setY(mMarginVertical);  // align top
    967                     mMainPanel.setY(0);  // align top
    968                     mOverflowButton.setY(0);  // align top
    969                     mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
    970                 }
    971             } else {
    972                 // Overflow not open. Set closed state.
    973                 final Size containerSize = mMainPanelSize;
    974                 setSize(mContentContainer, containerSize);
    975                 mMainPanel.setAlpha(1);
    976                 mMainPanel.setVisibility(View.VISIBLE);
    977                 mOverflowPanel.setAlpha(0);
    978                 mOverflowPanel.setVisibility(View.INVISIBLE);
    979                 mOverflowButton.setImageDrawable(mOverflow);
    980                 mOverflowButton.setContentDescription(mContext.getString(
    981                         R.string.floating_toolbar_open_overflow_description));
    982 
    983                 if (hasOverflow()) {
    984                     // Update x-coordinates depending on RTL state.
    985                     if (isInRTLMode()) {
    986                         mContentContainer.setX(mMarginHorizontal);  // align left
    987                         mMainPanel.setX(0);  // align left
    988                         mOverflowButton.setX(0);  // align left
    989                         mOverflowPanel.setX(0);  // align left
    990                     } else {
    991                         mContentContainer.setX(  // align right
    992                                 mPopupWindow.getWidth() -
    993                                         containerSize.getWidth() - mMarginHorizontal);
    994                         mMainPanel.setX(0);  // align left
    995                         mOverflowButton.setX(  // align right
    996                                 containerSize.getWidth() - mOverflowButtonSize.getWidth());
    997                         mOverflowPanel.setX(  // align right
    998                                 containerSize.getWidth() - mOverflowPanelSize.getWidth());
    999                     }
   1000 
   1001                     // Update y-coordinates depending on overflow's open direction.
   1002                     if (mOpenOverflowUpwards) {
   1003                         mContentContainer.setY(  // align bottom
   1004                                 mMarginVertical +
   1005                                         mOverflowPanelSize.getHeight() - containerSize.getHeight());
   1006                         mMainPanel.setY(0);  // align top
   1007                         mOverflowButton.setY(0);  // align top
   1008                         mOverflowPanel.setY(  // align bottom
   1009                                 containerSize.getHeight() - mOverflowPanelSize.getHeight());
   1010                     } else {
   1011                         // opens downwards.
   1012                         mContentContainer.setY(mMarginVertical);  // align top
   1013                         mMainPanel.setY(0);  // align top
   1014                         mOverflowButton.setY(0);  // align top
   1015                         mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
   1016                     }
   1017                 } else {
   1018                     // No overflow.
   1019                     mContentContainer.setX(mMarginHorizontal);  // align left
   1020                     mContentContainer.setY(mMarginVertical);  // align top
   1021                     mMainPanel.setX(0);  // align left
   1022                     mMainPanel.setY(0);  // align top
   1023                 }
   1024             }
   1025         }
   1026 
   1027         private void updateOverflowHeight(int suggestedHeight) {
   1028             if (hasOverflow()) {
   1029                 final int maxItemSize = (suggestedHeight - mOverflowButtonSize.getHeight()) /
   1030                         mLineHeight;
   1031                 final int newHeight = calculateOverflowHeight(maxItemSize);
   1032                 if (mOverflowPanelSize.getHeight() != newHeight) {
   1033                     mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight);
   1034                 }
   1035                 setSize(mOverflowPanel, mOverflowPanelSize);
   1036                 if (mIsOverflowOpen) {
   1037                     setSize(mContentContainer, mOverflowPanelSize);
   1038                     if (mOpenOverflowUpwards) {
   1039                         final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight;
   1040                         mContentContainer.setY(mContentContainer.getY() + deltaHeight);
   1041                         mOverflowButton.setY(mOverflowButton.getY() - deltaHeight);
   1042                     }
   1043                 } else {
   1044                     setSize(mContentContainer, mMainPanelSize);
   1045                 }
   1046                 updatePopupSize();
   1047             }
   1048         }
   1049 
   1050         private void updatePopupSize() {
   1051             int width = 0;
   1052             int height = 0;
   1053             if (mMainPanelSize != null) {
   1054                 width = Math.max(width, mMainPanelSize.getWidth());
   1055                 height = Math.max(height, mMainPanelSize.getHeight());
   1056             }
   1057             if (mOverflowPanelSize != null) {
   1058                 width = Math.max(width, mOverflowPanelSize.getWidth());
   1059                 height = Math.max(height, mOverflowPanelSize.getHeight());
   1060             }
   1061             mPopupWindow.setWidth(width + mMarginHorizontal * 2);
   1062             mPopupWindow.setHeight(height + mMarginVertical * 2);
   1063             maybeComputeTransitionDurationScale();
   1064         }
   1065 
   1066         private void refreshViewPort() {
   1067             mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen);
   1068         }
   1069 
   1070         private int getAdjustedToolbarWidth(int suggestedWidth) {
   1071             int width = suggestedWidth;
   1072             refreshViewPort();
   1073             int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources()
   1074                     .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
   1075             if (width <= 0) {
   1076                 width = mParent.getResources()
   1077                         .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width);
   1078             }
   1079             return Math.min(width, maximumWidth);
   1080         }
   1081 
   1082         /**
   1083          * Sets the touchable region of this popup to be zero. This means that all touch events on
   1084          * this popup will go through to the surface behind it.
   1085          */
   1086         private void setZeroTouchableSurface() {
   1087             mTouchableRegion.setEmpty();
   1088         }
   1089 
   1090         /**
   1091          * Sets the touchable region of this popup to be the area occupied by its content.
   1092          */
   1093         private void setContentAreaAsTouchableSurface() {
   1094             Preconditions.checkNotNull(mMainPanelSize);
   1095             final int width;
   1096             final int height;
   1097             if (mIsOverflowOpen) {
   1098                 Preconditions.checkNotNull(mOverflowPanelSize);
   1099                 width = mOverflowPanelSize.getWidth();
   1100                 height = mOverflowPanelSize.getHeight();
   1101             } else {
   1102                 width = mMainPanelSize.getWidth();
   1103                 height = mMainPanelSize.getHeight();
   1104             }
   1105             mTouchableRegion.set(
   1106                     (int) mContentContainer.getX(),
   1107                     (int) mContentContainer.getY(),
   1108                     (int) mContentContainer.getX() + width,
   1109                     (int) mContentContainer.getY() + height);
   1110         }
   1111 
   1112         /**
   1113          * Make the touchable area of this popup be the area specified by mTouchableRegion.
   1114          * This should be called after the popup window has been dismissed (dismiss/hide)
   1115          * and is probably being re-shown with a new content root view.
   1116          */
   1117         private void setTouchableSurfaceInsetsComputer() {
   1118             ViewTreeObserver viewTreeObserver = mPopupWindow.getContentView()
   1119                     .getRootView()
   1120                     .getViewTreeObserver();
   1121             viewTreeObserver.removeOnComputeInternalInsetsListener(mInsetsComputer);
   1122             viewTreeObserver.addOnComputeInternalInsetsListener(mInsetsComputer);
   1123         }
   1124 
   1125         private boolean isInRTLMode() {
   1126             return mContext.getApplicationInfo().hasRtlSupport()
   1127                     && mContext.getResources().getConfiguration().getLayoutDirection()
   1128                             == View.LAYOUT_DIRECTION_RTL;
   1129         }
   1130 
   1131         private boolean hasOverflow() {
   1132             return mOverflowPanelSize != null;
   1133         }
   1134 
   1135         /**
   1136          * Fits as many menu items in the main panel and returns a list of the menu items that
   1137          * were not fit in.
   1138          *
   1139          * @return The menu items that are not included in this main panel.
   1140          */
   1141         public List<MenuItem> layoutMainPanelItems(
   1142                 List<MenuItem> menuItems, final int toolbarWidth) {
   1143             Preconditions.checkNotNull(menuItems);
   1144 
   1145             int availableWidth = toolbarWidth;
   1146 
   1147             final LinkedList<MenuItem> remainingMenuItems = new LinkedList<>();
   1148             // add the overflow menu items to the end of the remainingMenuItems list.
   1149             final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
   1150             for (MenuItem menuItem : menuItems) {
   1151                 if (menuItem.requiresOverflow()) {
   1152                     overflowMenuItems.add(menuItem);
   1153                 } else {
   1154                     remainingMenuItems.add(menuItem);
   1155                 }
   1156             }
   1157             remainingMenuItems.addAll(overflowMenuItems);
   1158 
   1159             mMainPanel.removeAllViews();
   1160             mMainPanel.setPaddingRelative(0, 0, 0, 0);
   1161 
   1162             int lastGroupId = -1;
   1163             boolean isFirstItem = true;
   1164             while (!remainingMenuItems.isEmpty()) {
   1165                 final MenuItem menuItem = remainingMenuItems.peek();
   1166 
   1167                 // if this is the first item, regardless of requiresOverflow(), it should be
   1168                 // displayed on the main panel. Otherwise all items including this one will be
   1169                 // overflow items, and should be displayed in overflow panel.
   1170                 if(!isFirstItem && menuItem.requiresOverflow()) {
   1171                     break;
   1172                 }
   1173 
   1174                 View menuItemButton = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
   1175 
   1176                 // Adding additional start padding for the first button to even out button spacing.
   1177                 if (isFirstItem) {
   1178                     menuItemButton.setPaddingRelative(
   1179                             (int) (1.5 * menuItemButton.getPaddingStart()),
   1180                             menuItemButton.getPaddingTop(),
   1181                             menuItemButton.getPaddingEnd(),
   1182                             menuItemButton.getPaddingBottom());
   1183                 }
   1184 
   1185                 // Adding additional end padding for the last button to even out button spacing.
   1186                 boolean isLastItem = remainingMenuItems.size() == 1;
   1187                 if (isLastItem) {
   1188                     menuItemButton.setPaddingRelative(
   1189                             menuItemButton.getPaddingStart(),
   1190                             menuItemButton.getPaddingTop(),
   1191                             (int) (1.5 * menuItemButton.getPaddingEnd()),
   1192                             menuItemButton.getPaddingBottom());
   1193                 }
   1194 
   1195                 menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
   1196                 final int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
   1197 
   1198                 final boolean isNewGroup = !isFirstItem && lastGroupId != menuItem.getGroupId();
   1199                 final int extraPadding = isNewGroup ? menuItemButton.getPaddingEnd() * 2 : 0;
   1200 
   1201                 // Check if we can fit an item while reserving space for the overflowButton.
   1202                 boolean canFitWithOverflow =
   1203                         menuItemButtonWidth <=
   1204                                 availableWidth - mOverflowButtonSize.getWidth() - extraPadding;
   1205                 boolean canFitNoOverflow =
   1206                         isLastItem && menuItemButtonWidth <= availableWidth - extraPadding;
   1207                 if (canFitWithOverflow || canFitNoOverflow) {
   1208                     if (isNewGroup) {
   1209                         final View divider = createDivider(mContext);
   1210                         final int dividerWidth = divider.getLayoutParams().width;
   1211 
   1212                         // Add extra padding to the end of the previous button.
   1213                         // Half of the extra padding (less borderWidth) goes to the previous button.
   1214                         View previousButton = mMainPanel.getChildAt(mMainPanel.getChildCount() - 1);
   1215                         final int prevPaddingEnd = previousButton.getPaddingEnd()
   1216                                 + extraPadding / 2 - dividerWidth;
   1217                         previousButton.setPaddingRelative(
   1218                                 previousButton.getPaddingStart(),
   1219                                 previousButton.getPaddingTop(),
   1220                                 prevPaddingEnd,
   1221                                 previousButton.getPaddingBottom());
   1222                         final ViewGroup.LayoutParams prevParams = previousButton.getLayoutParams();
   1223                         prevParams.width += extraPadding / 2 - dividerWidth;
   1224                         previousButton.setLayoutParams(prevParams);
   1225 
   1226                         // Add extra padding to the start of this button.
   1227                         // Other half of the extra padding goes to this button.
   1228                         final int paddingStart = menuItemButton.getPaddingStart()
   1229                                 + extraPadding / 2;
   1230                         menuItemButton.setPaddingRelative(
   1231                                 paddingStart,
   1232                                 menuItemButton.getPaddingTop(),
   1233                                 menuItemButton.getPaddingEnd(),
   1234                                 menuItemButton.getPaddingBottom());
   1235 
   1236                         // Include a divider.
   1237                         mMainPanel.addView(divider);
   1238                     }
   1239 
   1240                     setButtonTagAndClickListener(menuItemButton, menuItem);
   1241                     // Set tooltips for main panel items, but not overflow items (b/35726766).
   1242                     menuItemButton.setTooltipText(menuItem.getTooltipText());
   1243                     mMainPanel.addView(menuItemButton);
   1244                     final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
   1245                     params.width = menuItemButtonWidth + extraPadding / 2;
   1246                     menuItemButton.setLayoutParams(params);
   1247                     availableWidth -= menuItemButtonWidth + extraPadding;
   1248                     remainingMenuItems.pop();
   1249                 } else {
   1250                     break;
   1251                 }
   1252                 lastGroupId = menuItem.getGroupId();
   1253                 isFirstItem = false;
   1254             }
   1255 
   1256             if (!remainingMenuItems.isEmpty()) {
   1257                 // Reserve space for overflowButton.
   1258                 mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
   1259             }
   1260 
   1261             mMainPanelSize = measure(mMainPanel);
   1262             return remainingMenuItems;
   1263         }
   1264 
   1265         private void layoutOverflowPanelItems(List<MenuItem> menuItems) {
   1266             ArrayAdapter<MenuItem> overflowPanelAdapter =
   1267                     (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
   1268             overflowPanelAdapter.clear();
   1269             final int size = menuItems.size();
   1270             for (int i = 0; i < size; i++) {
   1271                 overflowPanelAdapter.add(menuItems.get(i));
   1272             }
   1273             mOverflowPanel.setAdapter(overflowPanelAdapter);
   1274             if (mOpenOverflowUpwards) {
   1275                 mOverflowPanel.setY(0);
   1276             } else {
   1277                 mOverflowPanel.setY(mOverflowButtonSize.getHeight());
   1278             }
   1279 
   1280             int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
   1281             int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
   1282             mOverflowPanelSize = new Size(width, height);
   1283             setSize(mOverflowPanel, mOverflowPanelSize);
   1284         }
   1285 
   1286         /**
   1287          * Resets the content container and appropriately position it's panels.
   1288          */
   1289         private void preparePopupContent() {
   1290             mContentContainer.removeAllViews();
   1291 
   1292             // Add views in the specified order so they stack up as expected.
   1293             // Order: overflowPanel, mainPanel, overflowButton.
   1294             if (hasOverflow()) {
   1295                 mContentContainer.addView(mOverflowPanel);
   1296             }
   1297             mContentContainer.addView(mMainPanel);
   1298             if (hasOverflow()) {
   1299                 mContentContainer.addView(mOverflowButton);
   1300             }
   1301             setPanelsStatesAtRestingPosition();
   1302             setContentAreaAsTouchableSurface();
   1303 
   1304             // The positioning of contents in RTL is wrong when the view is first rendered.
   1305             // Hide the view and post a runnable to recalculate positions and render the view.
   1306             // TODO: Investigate why this happens and fix.
   1307             if (isInRTLMode()) {
   1308                 mContentContainer.setAlpha(0);
   1309                 mContentContainer.post(mPreparePopupContentRTLHelper);
   1310             }
   1311         }
   1312 
   1313         /**
   1314          * Clears out the panels and their container. Resets their calculated sizes.
   1315          */
   1316         private void clearPanels() {
   1317             mOverflowPanelSize = null;
   1318             mMainPanelSize = null;
   1319             mIsOverflowOpen = false;
   1320             mMainPanel.removeAllViews();
   1321             ArrayAdapter<MenuItem> overflowPanelAdapter =
   1322                     (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
   1323             overflowPanelAdapter.clear();
   1324             mOverflowPanel.setAdapter(overflowPanelAdapter);
   1325             mContentContainer.removeAllViews();
   1326         }
   1327 
   1328         private void positionContentYCoordinatesIfOpeningOverflowUpwards() {
   1329             if (mOpenOverflowUpwards) {
   1330                 mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight());
   1331                 mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight());
   1332                 mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight());
   1333             }
   1334         }
   1335 
   1336         private int getOverflowWidth() {
   1337             int overflowWidth = 0;
   1338             final int count = mOverflowPanel.getAdapter().getCount();
   1339             for (int i = 0; i < count; i++) {
   1340                 MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i);
   1341                 overflowWidth =
   1342                         Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
   1343             }
   1344             return overflowWidth;
   1345         }
   1346 
   1347         private int calculateOverflowHeight(int maxItemSize) {
   1348             // Maximum of 4 items, minimum of 2 if the overflow has to scroll.
   1349             int actualSize = Math.min(
   1350                     MAX_OVERFLOW_SIZE,
   1351                     Math.min(
   1352                             Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
   1353                             mOverflowPanel.getCount()));
   1354             int extension = 0;
   1355             if (actualSize < mOverflowPanel.getCount()) {
   1356                 // The overflow will require scrolling to get to all the items.
   1357                 // Extend the height so that part of the hidden items is displayed.
   1358                 extension = (int) (mLineHeight * 0.5f);
   1359             }
   1360             return actualSize * mLineHeight
   1361                     + mOverflowButtonSize.getHeight()
   1362                     + extension;
   1363         }
   1364 
   1365         private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
   1366             menuItemButton.setTag(menuItem);
   1367             menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
   1368         }
   1369 
   1370         /**
   1371          * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
   1372          * animations. See comment about this in the code.
   1373          */
   1374         private int getAdjustedDuration(int originalDuration) {
   1375             if (mTransitionDurationScale < 150) {
   1376                 // For smaller transition, decrease the time.
   1377                 return Math.max(originalDuration - 50, 0);
   1378             } else if (mTransitionDurationScale > 300) {
   1379                 // For bigger transition, increase the time.
   1380                 return originalDuration + 50;
   1381             }
   1382 
   1383             // Scale the animation duration with getDurationScale(). This allows
   1384             // android.view.animation.* animations to scale just like android.animation.* animations
   1385             // when  animator duration scale is adjusted in "Developer Options".
   1386             // For this reason, do not use this method for android.animation.* animations.
   1387             return (int) (originalDuration * ValueAnimator.getDurationScale());
   1388         }
   1389 
   1390         private void maybeComputeTransitionDurationScale() {
   1391             if (mMainPanelSize != null && mOverflowPanelSize != null) {
   1392                 int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth();
   1393                 int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight();
   1394                 mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h) /
   1395                         mContentContainer.getContext().getResources().getDisplayMetrics().density);
   1396             }
   1397         }
   1398 
   1399         private ViewGroup createMainPanel() {
   1400             ViewGroup mainPanel = new LinearLayout(mContext) {
   1401                 @Override
   1402                 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1403                     if (isOverflowAnimating()) {
   1404                         // Update widthMeasureSpec to make sure that this view is not clipped
   1405                         // as we offset it's coordinates with respect to it's parent.
   1406                         widthMeasureSpec = MeasureSpec.makeMeasureSpec(
   1407                                 mMainPanelSize.getWidth(),
   1408                                 MeasureSpec.EXACTLY);
   1409                     }
   1410                     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1411                 }
   1412 
   1413                 @Override
   1414                 public boolean onInterceptTouchEvent(MotionEvent ev) {
   1415                     // Intercept the touch event while the overflow is animating.
   1416                     return isOverflowAnimating();
   1417                 }
   1418             };
   1419             return mainPanel;
   1420         }
   1421 
   1422         private ImageButton createOverflowButton() {
   1423             final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext)
   1424                     .inflate(R.layout.floating_popup_overflow_button, null);
   1425             overflowButton.setImageDrawable(mOverflow);
   1426             overflowButton.setOnClickListener(v -> {
   1427                 if (mIsOverflowOpen) {
   1428                     overflowButton.setImageDrawable(mToOverflow);
   1429                     mToOverflow.start();
   1430                     closeOverflow();
   1431                 } else {
   1432                     overflowButton.setImageDrawable(mToArrow);
   1433                     mToArrow.start();
   1434                     openOverflow();
   1435                 }
   1436             });
   1437             return overflowButton;
   1438         }
   1439 
   1440         private OverflowPanel createOverflowPanel() {
   1441             final OverflowPanel overflowPanel = new OverflowPanel(this);
   1442             overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
   1443                     ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
   1444             overflowPanel.setDivider(null);
   1445             overflowPanel.setDividerHeight(0);
   1446 
   1447             final ArrayAdapter adapter =
   1448                     new ArrayAdapter<MenuItem>(mContext, 0) {
   1449                         @Override
   1450                         public View getView(int position, View convertView, ViewGroup parent) {
   1451                             return mOverflowPanelViewHelper.getView(
   1452                                     getItem(position), mOverflowPanelSize.getWidth(), convertView);
   1453                         }
   1454                     };
   1455             overflowPanel.setAdapter(adapter);
   1456 
   1457             overflowPanel.setOnItemClickListener((parent, view, position, id) -> {
   1458                 MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position);
   1459                 if (mOnMenuItemClickListener != null) {
   1460                     mOnMenuItemClickListener.onMenuItemClick(menuItem);
   1461                 }
   1462             });
   1463 
   1464             return overflowPanel;
   1465         }
   1466 
   1467         private boolean isOverflowAnimating() {
   1468             final boolean overflowOpening = mOpenOverflowAnimation.hasStarted()
   1469                     && !mOpenOverflowAnimation.hasEnded();
   1470             final boolean overflowClosing = mCloseOverflowAnimation.hasStarted()
   1471                     && !mCloseOverflowAnimation.hasEnded();
   1472             return overflowOpening || overflowClosing;
   1473         }
   1474 
   1475         private Animation.AnimationListener createOverflowAnimationListener() {
   1476             Animation.AnimationListener listener = new Animation.AnimationListener() {
   1477                 @Override
   1478                 public void onAnimationStart(Animation animation) {
   1479                     // Disable the overflow button while it's animating.
   1480                     // It will be re-enabled when the animation stops.
   1481                     mOverflowButton.setEnabled(false);
   1482                     // Ensure both panels have visibility turned on when the overflow animation
   1483                     // starts.
   1484                     mMainPanel.setVisibility(View.VISIBLE);
   1485                     mOverflowPanel.setVisibility(View.VISIBLE);
   1486                 }
   1487 
   1488                 @Override
   1489                 public void onAnimationEnd(Animation animation) {
   1490                     // Posting this because it seems like this is called before the animation
   1491                     // actually ends.
   1492                     mContentContainer.post(() -> {
   1493                         setPanelsStatesAtRestingPosition();
   1494                         setContentAreaAsTouchableSurface();
   1495                     });
   1496                 }
   1497 
   1498                 @Override
   1499                 public void onAnimationRepeat(Animation animation) {
   1500                 }
   1501             };
   1502             return listener;
   1503         }
   1504 
   1505         private static Size measure(View view) {
   1506             Preconditions.checkState(view.getParent() == null);
   1507             view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
   1508             return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
   1509         }
   1510 
   1511         private static void setSize(View view, int width, int height) {
   1512             view.setMinimumWidth(width);
   1513             view.setMinimumHeight(height);
   1514             ViewGroup.LayoutParams params = view.getLayoutParams();
   1515             params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params;
   1516             params.width = width;
   1517             params.height = height;
   1518             view.setLayoutParams(params);
   1519         }
   1520 
   1521         private static void setSize(View view, Size size) {
   1522             setSize(view, size.getWidth(), size.getHeight());
   1523         }
   1524 
   1525         private static void setWidth(View view, int width) {
   1526             ViewGroup.LayoutParams params = view.getLayoutParams();
   1527             setSize(view, width, params.height);
   1528         }
   1529 
   1530         private static void setHeight(View view, int height) {
   1531             ViewGroup.LayoutParams params = view.getLayoutParams();
   1532             setSize(view, params.width, height);
   1533         }
   1534 
   1535         /**
   1536          * A custom ListView for the overflow panel.
   1537          */
   1538         private static final class OverflowPanel extends ListView {
   1539 
   1540             private final FloatingToolbarPopup mPopup;
   1541 
   1542             OverflowPanel(FloatingToolbarPopup popup) {
   1543                 super(Preconditions.checkNotNull(popup).mContext);
   1544                 this.mPopup = popup;
   1545                 setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
   1546                 setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
   1547             }
   1548 
   1549             @Override
   1550             protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1551                 // Update heightMeasureSpec to make sure that this view is not clipped
   1552                 // as we offset it's coordinates with respect to it's parent.
   1553                 int height = mPopup.mOverflowPanelSize.getHeight()
   1554                         - mPopup.mOverflowButtonSize.getHeight();
   1555                 heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
   1556                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1557             }
   1558 
   1559             @Override
   1560             public boolean dispatchTouchEvent(MotionEvent ev) {
   1561                 if (mPopup.isOverflowAnimating()) {
   1562                     // Eat the touch event.
   1563                     return true;
   1564                 }
   1565                 return super.dispatchTouchEvent(ev);
   1566             }
   1567 
   1568             @Override
   1569             protected boolean awakenScrollBars() {
   1570                 return super.awakenScrollBars();
   1571             }
   1572         }
   1573 
   1574         /**
   1575          * A custom interpolator used for various floating toolbar animations.
   1576          */
   1577         private static final class LogAccelerateInterpolator implements Interpolator {
   1578 
   1579             private static final int BASE = 100;
   1580             private static final float LOGS_SCALE = 1f / computeLog(1, BASE);
   1581 
   1582             private static float computeLog(float t, int base) {
   1583                 return (float) (1 - Math.pow(base, -t));
   1584             }
   1585 
   1586             @Override
   1587             public float getInterpolation(float t) {
   1588                 return 1 - computeLog(1 - t, BASE) * LOGS_SCALE;
   1589             }
   1590         }
   1591 
   1592         /**
   1593          * A helper for generating views for the overflow panel.
   1594          */
   1595         private static final class OverflowPanelViewHelper {
   1596 
   1597             private final View mCalculator;
   1598             private final int mIconTextSpacing;
   1599             private final int mSidePadding;
   1600 
   1601             private final Context mContext;
   1602 
   1603             public OverflowPanelViewHelper(Context context) {
   1604                 mContext = Preconditions.checkNotNull(context);
   1605                 mIconTextSpacing = context.getResources()
   1606                         .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_side_padding);
   1607                 mSidePadding = context.getResources()
   1608                         .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding);
   1609                 mCalculator = createMenuButton(null);
   1610             }
   1611 
   1612             public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
   1613                 Preconditions.checkNotNull(menuItem);
   1614                 if (convertView != null) {
   1615                     updateMenuItemButton(convertView, menuItem, mIconTextSpacing);
   1616                 } else {
   1617                     convertView = createMenuButton(menuItem);
   1618                 }
   1619                 convertView.setMinimumWidth(minimumWidth);
   1620                 return convertView;
   1621             }
   1622 
   1623             public int calculateWidth(MenuItem menuItem) {
   1624                 updateMenuItemButton(mCalculator, menuItem, mIconTextSpacing);
   1625                 mCalculator.measure(
   1626                         View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
   1627                 return mCalculator.getMeasuredWidth();
   1628             }
   1629 
   1630             private View createMenuButton(MenuItem menuItem) {
   1631                 View button = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
   1632                 button.setPadding(mSidePadding, 0, mSidePadding, 0);
   1633                 return button;
   1634             }
   1635         }
   1636     }
   1637 
   1638     /**
   1639      * Creates and returns a menu button for the specified menu item.
   1640      */
   1641     private static View createMenuItemButton(
   1642             Context context, MenuItem menuItem, int iconTextSpacing) {
   1643         final View menuItemButton = LayoutInflater.from(context)
   1644                 .inflate(R.layout.floating_popup_menu_button, null);
   1645         if (menuItem != null) {
   1646             updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing);
   1647         }
   1648         return menuItemButton;
   1649     }
   1650 
   1651     /**
   1652      * Updates the specified menu item button with the specified menu item data.
   1653      */
   1654     private static void updateMenuItemButton(
   1655             View menuItemButton, MenuItem menuItem, int iconTextSpacing) {
   1656         final TextView buttonText = (TextView) menuItemButton.findViewById(
   1657                 R.id.floating_toolbar_menu_item_text);
   1658         if (TextUtils.isEmpty(menuItem.getTitle())) {
   1659             buttonText.setVisibility(View.GONE);
   1660         } else {
   1661             buttonText.setVisibility(View.VISIBLE);
   1662             buttonText.setText(menuItem.getTitle());
   1663         }
   1664         final ImageView buttonIcon = (ImageView) menuItemButton
   1665                 .findViewById(R.id.floating_toolbar_menu_item_image);
   1666         if (menuItem.getIcon() == null) {
   1667             buttonIcon.setVisibility(View.GONE);
   1668             if (buttonText != null) {
   1669                 buttonText.setPaddingRelative(0, 0, 0, 0);
   1670             }
   1671         } else {
   1672             buttonIcon.setVisibility(View.VISIBLE);
   1673             buttonIcon.setImageDrawable(menuItem.getIcon());
   1674             if (buttonText != null) {
   1675                 buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
   1676             }
   1677         }
   1678         final CharSequence contentDescription = menuItem.getContentDescription();
   1679         if (TextUtils.isEmpty(contentDescription)) {
   1680             menuItemButton.setContentDescription(menuItem.getTitle());
   1681         } else {
   1682             menuItemButton.setContentDescription(contentDescription);
   1683         }
   1684     }
   1685 
   1686     private static ViewGroup createContentContainer(Context context) {
   1687         ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
   1688                 .inflate(R.layout.floating_popup_container, null);
   1689         contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
   1690                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
   1691         contentContainer.setTag(FLOATING_TOOLBAR_TAG);
   1692         return contentContainer;
   1693     }
   1694 
   1695     private static PopupWindow createPopupWindow(ViewGroup content) {
   1696         ViewGroup popupContentHolder = new LinearLayout(content.getContext());
   1697         PopupWindow popupWindow = new PopupWindow(popupContentHolder);
   1698         // TODO: Use .setLayoutInScreenEnabled(true) instead of .setClippingEnabled(false)
   1699         // unless FLAG_LAYOUT_IN_SCREEN has any unintentional side-effects.
   1700         popupWindow.setClippingEnabled(false);
   1701         popupWindow.setWindowLayoutType(
   1702                 WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
   1703         popupWindow.setAnimationStyle(0);
   1704         popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
   1705         content.setLayoutParams(new ViewGroup.LayoutParams(
   1706                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
   1707         popupContentHolder.addView(content);
   1708         return popupWindow;
   1709     }
   1710 
   1711     private static View createDivider(Context context) {
   1712         // TODO: Inflate this instead.
   1713         View divider = new View(context);
   1714 
   1715         int _1dp = (int) TypedValue.applyDimension(
   1716                 TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
   1717         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
   1718                 _1dp, ViewGroup.LayoutParams.MATCH_PARENT);
   1719         params.setMarginsRelative(0, _1dp * 10, 0, _1dp * 10);
   1720         divider.setLayoutParams(params);
   1721 
   1722         TypedArray a = context.obtainStyledAttributes(
   1723                 new TypedValue().data, new int[] { R.attr.floatingToolbarDividerColor });
   1724         divider.setBackgroundColor(a.getColor(0, 0));
   1725         a.recycle();
   1726 
   1727         divider.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
   1728         divider.setEnabled(false);
   1729         divider.setFocusable(false);
   1730         divider.setContentDescription(null);
   1731 
   1732         return divider;
   1733     }
   1734 
   1735     /**
   1736      * Creates an "appear" animation for the specified view.
   1737      *
   1738      * @param view  The view to animate
   1739      */
   1740     private static AnimatorSet createEnterAnimation(View view) {
   1741         AnimatorSet animation = new AnimatorSet();
   1742         animation.playTogether(
   1743                 ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
   1744         return animation;
   1745     }
   1746 
   1747     /**
   1748      * Creates a "disappear" animation for the specified view.
   1749      *
   1750      * @param view  The view to animate
   1751      * @param startDelay  The start delay of the animation
   1752      * @param listener  The animation listener
   1753      */
   1754     private static AnimatorSet createExitAnimation(
   1755             View view, int startDelay, Animator.AnimatorListener listener) {
   1756         AnimatorSet animation =  new AnimatorSet();
   1757         animation.playTogether(
   1758                 ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100));
   1759         animation.setStartDelay(startDelay);
   1760         animation.addListener(listener);
   1761         return animation;
   1762     }
   1763 
   1764     /**
   1765      * Returns a re-themed context with controlled look and feel for views.
   1766      */
   1767     private static Context applyDefaultTheme(Context originalContext) {
   1768         TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
   1769         boolean isLightTheme = a.getBoolean(0, true);
   1770         int themeId = isLightTheme ? R.style.Theme_Material_Light : R.style.Theme_Material;
   1771         a.recycle();
   1772         return new ContextThemeWrapper(originalContext, themeId);
   1773     }
   1774 }
   1775