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