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