Merge "DO NOT MERGE: Hook up the CEC playback API to service internal logic." into lmp-preview-dev
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index f05f4c7..d4c4318 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -26,6 +26,7 @@
 import android.util.AttributeSet;
 import android.view.ActionMode;
 import android.view.Gravity;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -1013,6 +1014,26 @@
         return null;
     }
 
+    /** @hide */
+    public boolean openOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean invalidateOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean onMenuKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    /** @hide */
+    public boolean collapseActionView() {
+        return false;
+    }
+
     /**
      * Listener interface for ActionBar navigation events.
      *
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4c32989..23b5f29 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2083,7 +2083,8 @@
                     "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
                     "android:windowActionBar to false in your theme to use a Toolbar instead.");
         }
-        mActionBar = new ToolbarActionBar(toolbar);
+        mActionBar = new ToolbarActionBar(toolbar, getTitle(), this);
+        mActionBar.invalidateOptionsMenu();
     }
     
     /**
@@ -2449,6 +2450,10 @@
      * but you can override this to do whatever you want.
      */
     public void onBackPressed() {
+        if (mActionBar != null && mActionBar.collapseActionView()) {
+            return;
+        }
+
         if (!mFragments.popBackStackImmediate()) {
             finishAfterTransition();
         }
@@ -2660,6 +2665,14 @@
      */
     public boolean dispatchKeyEvent(KeyEvent event) {
         onUserInteraction();
+
+        // Let action bars open menus in response to the menu key prioritized over
+        // the window handling it
+        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
+                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
+            return true;
+        }
+
         Window win = getWindow();
         if (win.superDispatchKeyEvent(event)) {
             return true;
@@ -2907,7 +2920,9 @@
      * time it needs to be displayed.
      */
     public void invalidateOptionsMenu() {
-        mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+        if (mActionBar == null || !mActionBar.invalidateOptionsMenu()) {
+            mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+        }
     }
     
     /**
@@ -3117,7 +3132,9 @@
      * open, this method does nothing.
      */
     public void openOptionsMenu() {
-        mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+        if (mActionBar == null || !mActionBar.openOptionsMenu()) {
+            mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+        }
     }
     
     /**
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index a9a5eae..acee592 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -570,9 +570,9 @@
             mMenu = new MenuBuilder(context);
             mMenu.setCallback(new MenuBuilderCallback());
             mPresenter = new ActionMenuPresenter(context);
-            mPresenter.setMenuView(this);
             mPresenter.setCallback(new ActionMenuPresenterCallback());
             mMenu.addMenuPresenter(mPresenter);
+            mPresenter.setMenuView(this);
         }
 
         return mMenu;
@@ -652,6 +652,11 @@
         return false;
     }
 
+    /** @hide */
+    public void setExpandedActionViewsExclusive(boolean exclusive) {
+        mPresenter.setExpandedActionViewsExclusive(exclusive);
+    }
+
     /**
      * Interface responsible for receiving menu item click events if the items themselves
      * do not have individual item click listeners.
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index c41266e..419c582 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -222,7 +222,7 @@
 
         final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
         if (!TextUtils.isEmpty(subtitle)) {
-            setSubtitle(title);
+            setSubtitle(subtitle);
         }
         a.recycle();
     }
@@ -705,10 +705,23 @@
      * @return The toolbar's Menu
      */
     public Menu getMenu() {
-        ensureMenuView();
+        ensureMenu();
         return mMenuView.getMenu();
     }
 
+    private void ensureMenu() {
+        ensureMenuView();
+        if (mMenuView.peekMenu() == null) {
+            // Initialize a new menu for the first time.
+            final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu();
+            if (mExpandedMenuPresenter == null) {
+                mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+            }
+            mMenuView.setExpandedActionViewsExclusive(true);
+            menu.addMenuPresenter(mExpandedMenuPresenter);
+        }
+    }
+
     private void ensureMenuView() {
         if (mMenuView == null) {
             mMenuView = new ActionMenuView(getContext());
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index afb6f7c..6056bf2 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -23,37 +23,50 @@
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
 import android.view.ActionMode;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
+import android.view.Window;
 import android.widget.SpinnerAdapter;
 import android.widget.Toolbar;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.DecorToolbar;
+import com.android.internal.widget.ToolbarWidgetWrapper;
 
 import java.util.ArrayList;
-import java.util.Map;
 
 public class ToolbarActionBar extends ActionBar {
     private Toolbar mToolbar;
-    private View mCustomView;
-
-    private int mDisplayOptions;
-
-    private int mNavResId;
-    private int mIconResId;
-    private int mLogoResId;
-    private Drawable mNavDrawable;
-    private Drawable mIconDrawable;
-    private Drawable mLogoDrawable;
-    private int mTitleResId;
-    private int mSubtitleResId;
-    private CharSequence mTitle;
-    private CharSequence mSubtitle;
+    private DecorToolbar mDecorToolbar;
+    private Window.Callback mWindowCallback;
 
     private boolean mLastMenuVisibility;
     private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
             new ArrayList<OnMenuVisibilityListener>();
 
-    public ToolbarActionBar(Toolbar toolbar) {
+    private final Runnable mMenuInvalidator = new Runnable() {
+        @Override
+        public void run() {
+            populateOptionsMenu();
+        }
+    };
+
+    private final Toolbar.OnMenuItemClickListener mMenuClicker =
+            new Toolbar.OnMenuItemClickListener() {
+        @Override
+        public boolean onMenuItemClick(MenuItem item) {
+            return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
+        }
+    };
+
+    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
         mToolbar = toolbar;
+        mDecorToolbar = new ToolbarWidgetWrapper(toolbar);
+        mWindowCallback = windowCallback;
+        toolbar.setOnMenuItemClickListener(mMenuClicker);
+        mDecorToolbar.setWindowTitle(title);
     }
 
     @Override
@@ -63,19 +76,8 @@
 
     @Override
     public void setCustomView(View view, LayoutParams layoutParams) {
-        if (mCustomView != null) {
-            mToolbar.removeView(mCustomView);
-        }
-        mCustomView = view;
-        if (view != null) {
-            mToolbar.addView(view, generateLayoutParams(layoutParams));
-        }
-    }
-
-    private Toolbar.LayoutParams generateLayoutParams(LayoutParams lp) {
-        final Toolbar.LayoutParams result = new Toolbar.LayoutParams(lp);
-        result.gravity = lp.gravity;
-        return result;
+        view.setLayoutParams(layoutParams);
+        mDecorToolbar.setCustomView(view);
     }
 
     @Override
@@ -86,48 +88,22 @@
 
     @Override
     public void setIcon(int resId) {
-        mIconResId = resId;
-        mIconDrawable = null;
-        updateToolbarLogo();
+        mDecorToolbar.setIcon(resId);
     }
 
     @Override
     public void setIcon(Drawable icon) {
-        mIconResId = 0;
-        mIconDrawable = icon;
-        updateToolbarLogo();
+        mDecorToolbar.setIcon(icon);
     }
 
     @Override
     public void setLogo(int resId) {
-        mLogoResId = resId;
-        mLogoDrawable = null;
-        updateToolbarLogo();
+        mDecorToolbar.setLogo(resId);
     }
 
     @Override
     public void setLogo(Drawable logo) {
-        mLogoResId = 0;
-        mLogoDrawable = logo;
-        updateToolbarLogo();
-    }
-
-    private void updateToolbarLogo() {
-        Drawable drawable = null;
-        if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
-            final int resId;
-            if ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
-                resId = mLogoResId;
-                drawable = mLogoDrawable;
-            } else {
-                resId = mIconResId;
-                drawable = mIconDrawable;
-            }
-            if (resId != 0) {
-                drawable = mToolbar.getContext().getDrawable(resId);
-            }
-        }
-        mToolbar.setLogo(drawable);
+        mDecorToolbar.setLogo(logo);
     }
 
     @Override
@@ -219,42 +195,22 @@
 
     @Override
     public void setTitle(CharSequence title) {
-        mTitle = title;
-        mTitleResId = 0;
-        updateToolbarTitle();
+        mDecorToolbar.setTitle(title);
     }
 
     @Override
     public void setTitle(int resId) {
-        mTitleResId = resId;
-        mTitle = null;
-        updateToolbarTitle();
+        mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
     }
 
     @Override
     public void setSubtitle(CharSequence subtitle) {
-        mSubtitle = subtitle;
-        mSubtitleResId = 0;
-        updateToolbarTitle();
+        mDecorToolbar.setSubtitle(subtitle);
     }
 
     @Override
     public void setSubtitle(int resId) {
-        mSubtitleResId = resId;
-        mSubtitle = null;
-        updateToolbarTitle();
-    }
-
-    private void updateToolbarTitle() {
-        final Context context = mToolbar.getContext();
-        CharSequence title = null;
-        CharSequence subtitle = null;
-        if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
-            title = mTitleResId != 0 ? context.getText(mTitleResId) : mTitle;
-            subtitle = mSubtitleResId != 0 ? context.getText(mSubtitleResId) : mSubtitle;
-        }
-        mToolbar.setTitle(title);
-        mToolbar.setSubtitle(subtitle);
+        mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
     }
 
     @Override
@@ -264,9 +220,8 @@
 
     @Override
     public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
-        final int oldOptions = mDisplayOptions;
-        mDisplayOptions = (options & mask) | (mDisplayOptions & ~mask);
-        final int optionsChanged = oldOptions ^ mDisplayOptions;
+        mDecorToolbar.setDisplayOptions((options & mask) |
+                mDecorToolbar.getDisplayOptions() & ~mask);
     }
 
     @Override
@@ -301,7 +256,7 @@
 
     @Override
     public View getCustomView() {
-        return mCustomView;
+        return mDecorToolbar.getCustomView();
     }
 
     @Override
@@ -327,7 +282,7 @@
 
     @Override
     public int getDisplayOptions() {
-        return mDisplayOptions;
+        return mDecorToolbar.getDisplayOptions();
     }
 
     @Override
@@ -425,6 +380,54 @@
         return mToolbar.getVisibility() == View.VISIBLE;
     }
 
+    @Override
+    public boolean openOptionsMenu() {
+        return mToolbar.showOverflowMenu();
+    }
+
+    @Override
+    public boolean invalidateOptionsMenu() {
+        mToolbar.removeCallbacks(mMenuInvalidator);
+        mToolbar.postOnAnimation(mMenuInvalidator);
+        return true;
+    }
+
+    @Override
+    public boolean collapseActionView() {
+        if (mToolbar.hasExpandedActionView()) {
+            mToolbar.collapseActionView();
+            return true;
+        }
+        return false;
+    }
+
+    void populateOptionsMenu() {
+        final Menu menu = mToolbar.getMenu();
+        final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
+        if (mb != null) {
+            mb.stopDispatchingItemsChanged();
+        }
+        try {
+            menu.clear();
+            if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) ||
+                    !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
+                menu.clear();
+            }
+        } finally {
+            if (mb != null) {
+                mb.startDispatchingItemsChanged();
+            }
+        }
+    }
+
+    @Override
+    public boolean onMenuKeyEvent(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_UP) {
+            openOptionsMenu();
+        }
+        return true;
+    }
+
     public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
         mMenuVisibilityListeners.add(listener);
     }
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index b8dc307..3e15c32 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -24,6 +24,7 @@
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
@@ -80,16 +81,20 @@
     public ToolbarWidgetWrapper(Toolbar toolbar) {
         mToolbar = toolbar;
 
+        mTitle = toolbar.getTitle();
+        mSubtitle = toolbar.getSubtitle();
+        mTitleSet = !TextUtils.isEmpty(mTitle);
+
         final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
                 R.styleable.ActionBar, R.attr.actionBarStyle, 0);
 
         final CharSequence title = a.getText(R.styleable.ActionBar_title);
-        if (title != null) {
+        if (!TextUtils.isEmpty(title)) {
             setTitle(title);
         }
 
         final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
-        if (subtitle != null) {
+        if (!TextUtils.isEmpty(subtitle)) {
             setSubtitle(subtitle);
         }
 
diff --git a/core/res/res/layout/alert_dialog_quantum.xml b/core/res/res/layout/alert_dialog_quantum.xml
index e109425f4..7fd22ad 100644
--- a/core/res/res/layout/alert_dialog_quantum.xml
+++ b/core/res/res/layout/alert_dialog_quantum.xml
@@ -23,7 +23,10 @@
     android:orientation="vertical"
     android:background="@drawable/dialog_background_quantum"
     android:translationZ="@dimen/floating_window_z"
-    android:layout_margin="@dimen/floating_window_margin">
+    android:layout_marginLeft="@dimen/floating_window_margin_left"
+    android:layout_marginTop="@dimen/floating_window_margin_top"
+    android:layout_marginRight="@dimen/floating_window_margin_right"
+    android:layout_marginBottom="@dimen/floating_window_margin_bottom">
 
     <LinearLayout android:id="@+id/topPanel"
         android:layout_width="match_parent"
@@ -44,7 +47,7 @@
                 android:scaleType="fitCenter"
                 android:src="@null" />
             <TextView android:id="@+id/alertTitle"
-                style="?android:attr/windowTitleStyle"
+                style="?attr/windowTitleStyle"
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:layout_width="match_parent"
@@ -65,7 +68,7 @@
             android:layout_height="wrap_content"
             android:clipToPadding="false">
             <TextView android:id="@+id/message"
-                style="?android:attr/textAppearanceMedium"
+                style="?attr/textAppearanceMedium"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:paddingStart="16dip"
@@ -92,26 +95,24 @@
         android:gravity="end"
         android:padding="16dip">
         <LinearLayout
-            style="?android:attr/buttonBarStyle"
+            style="?attr/buttonBarStyle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layoutDirection="locale">
             <Button android:id="@+id/button3"
-                style="?android:attr/buttonBarButtonStyle"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginRight="8dip"
                 android:maxLines="2"
                 android:minHeight="@dimen/alert_dialog_button_bar_height" />
             <Button android:id="@+id/button2"
-                style="?android:attr/buttonBarButtonStyle"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:maxLines="2"
                 android:minHeight="@dimen/alert_dialog_button_bar_height" />
             <Button android:id="@+id/button1"
-                style="?android:attr/buttonBarButtonStyle"
-                android:layout_marginLeft="8dip"
+                style="?attr/buttonBarButtonStyle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:maxLines="2"
diff --git a/core/res/res/values/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml
index 2defee2..b5ba1ca 100644
--- a/core/res/res/values/dimens_quantum.xml
+++ b/core/res/res/values/dimens_quantum.xml
@@ -52,7 +52,10 @@
     <dimen name="text_size_small_quantum">14sp</dimen>
 
     <dimen name="floating_window_z">16dp</dimen>
-    <dimen name="floating_window_margin">32dp</dimen>
+    <dimen name="floating_window_margin_left">16dp</dimen>
+    <dimen name="floating_window_margin_top">8dp</dimen>
+    <dimen name="floating_window_margin_right">16dp</dimen>
+    <dimen name="floating_window_margin_bottom">32dp</dimen>
 
     <!-- the amount of elevation for pressed button state-->
     <dimen name="button_pressed_z">2dp</dimen>
diff --git a/core/res/res/values/donottranslate_quantum.xml b/core/res/res/values/donottranslate_quantum.xml
index 83cc4e5..e53c40e 100644
--- a/core/res/res/values/donottranslate_quantum.xml
+++ b/core/res/res/values/donottranslate_quantum.xml
@@ -27,6 +27,6 @@
     <string name="font_family_body_1_quantum">sans-serif</string>
     <string name="font_family_caption_quantum">sans-serif</string>
     <string name="font_family_menu_quantum">sans-serif-medium</string>
-    <string name="font_family_button_quantum">sans-serif</string>
+    <string name="font_family_button_quantum">sans-serif-medium</string>
 
 </resources>
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index b55edf8..2f51048 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -404,7 +404,7 @@
         <item name="textAppearance">?attr/textAppearanceButton</item>
         <item name="textColor">?attr/textColorPrimary</item>
         <item name="minHeight">48dip</item>
-        <item name="minWidth">96dip</item>
+        <item name="minWidth">88dip</item>
 
         <!-- TODO: Turn this back on when we support inset drawable outlines. -->
         <!-- <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item> -->
diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index 7d6bbdf..cdbd771 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -668,7 +668,7 @@
         <item name="dividerVertical">?attr/listDivider</item>
         <item name="dividerHorizontal">?attr/listDivider</item>
         <item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar</item>
-        <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item>
         <item name="segmentedButtonStyle">@style/Widget.Quantum.Light.SegmentedButton</item>
 
         <!-- SearchView attributes -->
@@ -1004,7 +1004,7 @@
         <item name="colorBackgroundCacheHint">@null</item>
 
         <item name="buttonBarStyle">@style/Widget.Quantum.ButtonBar.AlertDialog</item>
-        <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless.Small</item>
+        <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item>
 
         <item name="textAppearance">@style/TextAppearance.Quantum</item>
         <item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item>
@@ -1123,7 +1123,7 @@
         <item name="colorBackgroundCacheHint">@null</item>
 
         <item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar.AlertDialog</item>
-        <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item>
+        <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item>
 
         <item name="textAppearance">@style/TextAppearance.Quantum</item>
         <item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item>
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 65b6814..ada741b 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -85,15 +85,13 @@
     // Software rendering properties.
     private float mOuterOpacity = 0;
     private float mOpacity = 1;
-    private float mRadius = 0;
     private float mOuterX;
     private float mOuterY;
-    private float mX;
-    private float mY;
 
     // Values used to tween between the start and end positions.
-    private float mXGravity = 0;
-    private float mYGravity = 0;
+    private float mTweenRadius = 0;
+    private float mTweenX = 0;
+    private float mTweenY = 0;
 
     /** Whether we should be drawing hardware animations. */
     private boolean mHardwareAnimating;
@@ -101,6 +99,9 @@
     /** Whether we can use hardware acceleration for the exit animation. */
     private boolean mCanUseHardware;
 
+    /** Whether we have an explicit maximum radius. */
+    private boolean mHasMaxRadius;
+
     /**
      * Creates a new ripple.
      */
@@ -115,6 +116,7 @@
         mColor = color | 0xFF000000;
 
         if (maxRadius != RippleDrawable.RADIUS_AUTO) {
+            mHasMaxRadius = true;
             mOuterRadius = maxRadius;
         } else {
             final float halfWidth = mBounds.width() / 2.0f;
@@ -127,13 +129,12 @@
         mDensity = density;
     }
 
-    public void setRadius(float r) {
-        mRadius = r;
-        invalidateSelf();
-    }
-
-    public float getRadius() {
-        return mRadius;
+    public void onHotspotBoundsChanged() {
+        if (!mHasMaxRadius) {
+            final float halfWidth = mBounds.width() / 2.0f;
+            final float halfHeight = mBounds.height() / 2.0f;
+            mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+        }
     }
 
     public void setOpacity(float a) {
@@ -154,40 +155,31 @@
         return mOuterOpacity;
     }
 
+    public void setRadiusGravity(float r) {
+        mTweenRadius = r;
+        invalidateSelf();
+    }
+
+    public float getRadiusGravity() {
+        return mTweenRadius;
+    }
+
     public void setXGravity(float x) {
-        mXGravity = x;
+        mTweenX = x;
         invalidateSelf();
     }
 
     public float getXGravity() {
-        return mXGravity;
+        return mTweenX;
     }
 
     public void setYGravity(float y) {
-        mYGravity = y;
+        mTweenY = y;
         invalidateSelf();
     }
 
     public float getYGravity() {
-        return mYGravity;
-    }
-
-    public void setX(float x) {
-        mX = x;
-        invalidateSelf();
-    }
-
-    public float getX() {
-        return mX;
-    }
-
-    public void setY(float y) {
-        mY = y;
-        invalidateSelf();
-    }
-
-    public float getY() {
-        return mY;
+        return mTweenY;
     }
 
     /**
@@ -249,12 +241,13 @@
         }
 
         final int alpha = (int) (255 * mOpacity + 0.5f);
-        if (alpha > 0 && mRadius > 0) {
-            final float x = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mXGravity);
-            final float y = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mYGravity);
+        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
+        if (alpha > 0 && radius > 0) {
+            final float x = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+            final float y = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
             p.setAlpha(alpha);
             p.setStyle(Style.FILL);
-            c.drawCircle(x, y, mRadius, p);
+            c.drawCircle(x, y, radius, p);
             hasContent = true;
         }
 
@@ -264,17 +257,13 @@
     }
 
     /**
-     * Returns the maximum bounds for this ripple.
+     * Returns the maximum bounds of the ripple relative to the ripple center.
      */
     public void getBounds(Rect bounds) {
         final int outerX = (int) mOuterX;
         final int outerY = (int) mOuterY;
         final int r = (int) mOuterRadius;
         bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
-
-        final int x = (int) mX;
-        final int y = (int) mY;
-        bounds.union(x - r, y - r, x + r, y + r);
     }
 
     /**
@@ -290,14 +279,11 @@
      * Starts the enter animation.
      */
     public void enter() {
-        mX = mStartingX - mBounds.exactCenterX();
-        mY = mStartingY - mBounds.exactCenterY();
-
         final int radiusDuration = (int)
                 (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
         final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY);
 
-        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", 0, mOuterRadius);
+        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
         radius.setAutoCancel(true);
         radius.setDuration(radiusDuration);
         radius.setInterpolator(LINEAR_INTERPOLATOR);
@@ -335,12 +321,10 @@
     public void exit() {
         cancelSoftwareAnimations();
 
-        mX = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mXGravity);
-        mY = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mYGravity);
-
+        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         final float remaining;
         if (mAnimRadius != null && mAnimRadius.isRunning()) {
-            remaining = mOuterRadius - mRadius;
+            remaining = mOuterRadius - radius;
         } else {
             remaining = mOuterRadius;
         }
@@ -368,6 +352,8 @@
             int inflectionOpacity) {
         mPendingAnimations.clear();
 
+        final float startX = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+        final float startY = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
         final Paint outerPaint = new Paint();
         outerPaint.setAntiAlias(true);
         outerPaint.setColor(mColor);
@@ -378,68 +364,69 @@
         mPropOuterX = CanvasProperty.createFloat(mOuterX);
         mPropOuterY = CanvasProperty.createFloat(mOuterY);
 
+        final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
         paint.setColor(mColor);
         paint.setAlpha((int) (255 * mOpacity + 0.5f));
         paint.setStyle(Style.FILL);
         mPropPaint = CanvasProperty.createPaint(paint);
-        mPropRadius = CanvasProperty.createFloat(mRadius);
-        mPropX = CanvasProperty.createFloat(mX);
-        mPropY = CanvasProperty.createFloat(mY);
+        mPropRadius = CanvasProperty.createFloat(startRadius);
+        mPropX = CanvasProperty.createFloat(startX);
+        mPropY = CanvasProperty.createFloat(startY);
 
-        final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mOuterRadius);
-        radius.setDuration(radiusDuration);
-        radius.setInterpolator(LINEAR_INTERPOLATOR);
+        final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
+        radiusAnim.setDuration(radiusDuration);
+        radiusAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX);
-        x.setDuration(radiusDuration);
-        x.setInterpolator(LINEAR_INTERPOLATOR);
+        final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
+        xAnim.setDuration(radiusDuration);
+        xAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY);
-        y.setDuration(radiusDuration);
-        y.setInterpolator(LINEAR_INTERPOLATOR);
+        final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
+        yAnim.setDuration(radiusDuration);
+        yAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+        final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
                 RenderNodeAnimator.PAINT_ALPHA, 0);
-        opacity.setDuration(opacityDuration);
-        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        opacityAnim.setDuration(opacityDuration);
+        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final RenderNodeAnimator outerOpacity;
+        final RenderNodeAnimator outerOpacityAnim;
         if (outerInflection > 0) {
             // Outer opacity continues to increase for a bit.
-            outerOpacity = new RenderNodeAnimator(
+            outerOpacityAnim = new RenderNodeAnimator(
                     mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
-            outerOpacity.setDuration(outerInflection);
-            outerOpacity.setInterpolator(LINEAR_INTERPOLATOR);
+            outerOpacityAnim.setDuration(outerInflection);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
             // Chain the outer opacity exit animation.
             final int outerDuration = opacityDuration - outerInflection;
             if (outerDuration > 0) {
-                final RenderNodeAnimator outerFadeOut = new RenderNodeAnimator(
+                final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
                         mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-                outerFadeOut.setDuration(outerDuration);
-                outerFadeOut.setInterpolator(LINEAR_INTERPOLATOR);
-                outerFadeOut.setStartDelay(outerInflection);
-                outerFadeOut.addListener(mAnimationListener);
+                outerFadeOutAnim.setDuration(outerDuration);
+                outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+                outerFadeOutAnim.setStartDelay(outerInflection);
+                outerFadeOutAnim.addListener(mAnimationListener);
 
-                mPendingAnimations.add(outerFadeOut);
+                mPendingAnimations.add(outerFadeOutAnim);
             } else {
-                outerOpacity.addListener(mAnimationListener);
+                outerOpacityAnim.addListener(mAnimationListener);
             }
         } else {
-            outerOpacity = new RenderNodeAnimator(
+            outerOpacityAnim = new RenderNodeAnimator(
                     mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
-            outerOpacity.setInterpolator(LINEAR_INTERPOLATOR);
-            outerOpacity.setDuration(opacityDuration);
-            outerOpacity.addListener(mAnimationListener);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+            outerOpacityAnim.setDuration(opacityDuration);
+            outerOpacityAnim.addListener(mAnimationListener);
         }
 
-        mPendingAnimations.add(radius);
-        mPendingAnimations.add(opacity);
-        mPendingAnimations.add(outerOpacity);
-        mPendingAnimations.add(x);
-        mPendingAnimations.add(y);
+        mPendingAnimations.add(radiusAnim);
+        mPendingAnimations.add(opacityAnim);
+        mPendingAnimations.add(outerOpacityAnim);
+        mPendingAnimations.add(xAnim);
+        mPendingAnimations.add(yAnim);
 
         mHardwareAnimating = true;
 
@@ -448,51 +435,51 @@
 
     private void exitSoftware(int radiusDuration, int opacityDuration, int outerInflection,
             int inflectionOpacity) {
-        final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", mOuterRadius);
-        radius.setAutoCancel(true);
-        radius.setDuration(radiusDuration);
-        radius.setInterpolator(LINEAR_INTERPOLATOR);
+        final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
+        radiusAnim.setAutoCancel(true);
+        radiusAnim.setDuration(radiusDuration);
+        radiusAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator x = ObjectAnimator.ofFloat(this, "x", mOuterX);
-        x.setAutoCancel(true);
-        x.setDuration(radiusDuration);
-        x.setInterpolator(LINEAR_INTERPOLATOR);
+        final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
+        xAnim.setAutoCancel(true);
+        xAnim.setDuration(radiusDuration);
+        xAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator y = ObjectAnimator.ofFloat(this, "y", mOuterY);
-        y.setAutoCancel(true);
-        y.setDuration(radiusDuration);
-        y.setInterpolator(LINEAR_INTERPOLATOR);
+        final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
+        yAnim.setAutoCancel(true);
+        yAnim.setDuration(radiusDuration);
+        yAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "opacity", 0);
-        opacity.setAutoCancel(true);
-        opacity.setDuration(opacityDuration);
-        opacity.setInterpolator(LINEAR_INTERPOLATOR);
+        final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0);
+        opacityAnim.setAutoCancel(true);
+        opacityAnim.setDuration(opacityDuration);
+        opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
-        final ObjectAnimator outerOpacity;
+        final ObjectAnimator outerOpacityAnim;
         if (outerInflection > 0) {
             // Outer opacity continues to increase for a bit.
-            outerOpacity = ObjectAnimator.ofFloat(this,
+            outerOpacityAnim = ObjectAnimator.ofFloat(this,
                     "outerOpacity", inflectionOpacity / 255.0f);
-            outerOpacity.setAutoCancel(true);
-            outerOpacity.setDuration(outerInflection);
-            outerOpacity.setInterpolator(LINEAR_INTERPOLATOR);
+            outerOpacityAnim.setAutoCancel(true);
+            outerOpacityAnim.setDuration(outerInflection);
+            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
 
             // Chain the outer opacity exit animation.
             final int outerDuration = opacityDuration - outerInflection;
             if (outerDuration > 0) {
-                outerOpacity.addListener(new AnimatorListenerAdapter() {
+                outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
-                        final ObjectAnimator outerFadeOut = ObjectAnimator.ofFloat(Ripple.this,
+                        final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(Ripple.this,
                                 "outerOpacity", 0);
-                        outerFadeOut.setAutoCancel(true);
-                        outerFadeOut.setDuration(outerDuration);
-                        outerFadeOut.setInterpolator(LINEAR_INTERPOLATOR);
-                        outerFadeOut.addListener(mAnimationListener);
+                        outerFadeOutAnim.setAutoCancel(true);
+                        outerFadeOutAnim.setDuration(outerDuration);
+                        outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+                        outerFadeOutAnim.addListener(mAnimationListener);
 
-                        mAnimOuterOpacity = outerFadeOut;
+                        mAnimOuterOpacity = outerFadeOutAnim;
 
-                        outerFadeOut.start();
+                        outerFadeOutAnim.start();
                     }
 
                     @Override
@@ -501,26 +488,26 @@
                     }
                 });
             } else {
-                outerOpacity.addListener(mAnimationListener);
+                outerOpacityAnim.addListener(mAnimationListener);
             }
         } else {
-            outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
-            outerOpacity.setAutoCancel(true);
-            outerOpacity.setDuration(opacityDuration);
-            outerOpacity.addListener(mAnimationListener);
+            outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
+            outerOpacityAnim.setAutoCancel(true);
+            outerOpacityAnim.setDuration(opacityDuration);
+            outerOpacityAnim.addListener(mAnimationListener);
         }
 
-        mAnimRadius = radius;
-        mAnimOpacity = opacity;
-        mAnimOuterOpacity = outerOpacity;
-        mAnimX = opacity;
-        mAnimY = opacity;
+        mAnimRadius = radiusAnim;
+        mAnimOpacity = opacityAnim;
+        mAnimOuterOpacity = outerOpacityAnim;
+        mAnimX = opacityAnim;
+        mAnimY = opacityAnim;
 
-        radius.start();
-        opacity.start();
-        outerOpacity.start();
-        x.start();
-        y.start();
+        radiusAnim.start();
+        opacityAnim.start();
+        outerOpacityAnim.start();
+        xAnim.start();
+        yAnim.start();
     }
 
     /**
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 4e786f0..9d7a8b6 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -213,6 +213,7 @@
 
         if (!mOverrideBounds) {
             mHotspotBounds.set(bounds);
+            onHotspotBoundsChanged();
         }
 
         invalidateSelf();
@@ -452,6 +453,19 @@
     public void setHotspotBounds(int left, int top, int right, int bottom) {
         mOverrideBounds = true;
         mHotspotBounds.set(left, top, right, bottom);
+
+        onHotspotBoundsChanged();
+    }
+
+    /**
+     * Notifies all the animating ripples that the hotspot bounds have changed.
+     */
+    private void onHotspotBoundsChanged() {
+        final int count = mAnimatingRipplesCount;
+        final Ripple[] ripples = mAnimatingRipples;
+        for (int i = 0; i < count; i++) {
+            ripples[i].onHotspotBoundsChanged();
+        }
     }
 
     @Override