Merged KeyButtonDrawable with tint drawable and shadow drawable (1/2)

KeyButtonDrawable can now draw its own shadow and tint based on its dark
intensity, no more shadow drawables wrapped by dual layer drawable and
cross fading to change their intensities. Refactored and simplified code
to get the nav bar drawables. AnimatedVectorDrawables can be used however
only dark intensity tinting will work on it. Fixed the clipped shadow in
landscape.

Change-Id: I6e234857f7972974aae34d92c7047086782124f0
Fixes: 112105450
Test: look at nav bar and tap rotate suggestion button
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 91512dd..b7eee36 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -262,16 +262,10 @@
             }
 
             if (navigationBarView != null) {
-                int dualToneDarkTheme = Utils.getThemeAttr(getContext(), R.attr.darkIconTheme);
-                int dualToneLightTheme = Utils.getThemeAttr(getContext(), R.attr.lightIconTheme);
-                Context lightContext = new ContextThemeWrapper(getContext(), dualToneLightTheme);
-                Context darkContext = new ContextThemeWrapper(getContext(), dualToneDarkTheme);
                 ((ImageView) mLayout.findViewById(R.id.screen_pinning_back_icon))
-                        .setImageDrawable(navigationBarView.getBackDrawable(lightContext,
-                                darkContext));
+                        .setImageDrawable(navigationBarView.getBackDrawable());
                 ((ImageView) mLayout.findViewById(R.id.screen_pinning_home_icon))
-                        .setImageDrawable(navigationBarView.getHomeDrawable(lightContext,
-                                darkContext));
+                        .setImageDrawable(navigationBarView.getHomeDrawable());
             }
 
             ((TextView) mLayout.findViewById(R.id.screen_pinning_description))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index b84ee03..10b019d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -49,7 +49,6 @@
 import android.database.ContentObserver;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
 import android.inputmethodservice.InputMethodService;
 import android.os.Binder;
 import android.os.Bundle;
@@ -532,12 +531,6 @@
         KeyButtonDrawable kbd = rotBtn.getImageDrawable();
         if (kbd == null) return;
 
-        // The KBD and AVD is recreated every new valid suggestion because of style changes.
-        AnimatedVectorDrawable animIcon = null;
-        if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) {
-            animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
-        }
-
         // Clear any pending suggestion flag as it has either been nullified or is being shown
         mPendingRotationSuggestion = false;
         if (getView() != null) getView().removeCallbacks(mCancelPendingRotationProposal);
@@ -554,9 +547,9 @@
             view.setAlpha(1f);
 
             // Run the rotate icon's animation if it has one
-            if (animIcon != null) {
-                animIcon.reset();
-                animIcon.start();
+            if (kbd.canAnimate()) {
+                kbd.resetAnimation();
+                kbd.startAnimation();
             }
 
             if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 1892d37..2141016 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -80,7 +80,6 @@
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.policy.DeadZone;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
-import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -124,14 +123,12 @@
 
     private KeyButtonDrawable mBackIcon;
     private KeyButtonDrawable mHomeDefaultIcon;
-    private KeyButtonDrawable mBackCarModeIcon;
-    private KeyButtonDrawable mHomeCarModeIcon;
     private KeyButtonDrawable mRecentIcon;
     private KeyButtonDrawable mDockedIcon;
     private KeyButtonDrawable mImeIcon;
     private KeyButtonDrawable mMenuIcon;
     private KeyButtonDrawable mAccessibilityIcon;
-    private TintedKeyButtonDrawable mRotateSuggestionIcon;
+    private KeyButtonDrawable mRotateSuggestionIcon;
 
     private GestureHelper mGestureHelper;
     private final DeadZone mDeadZone;
@@ -461,62 +458,47 @@
                 && ((mOverviewProxyService.getInteractionFlags() & FLAG_DISABLE_QUICK_SCRUB) == 0);
     }
 
-    private void updateCarModeIcons(Context ctx) {
-        mBackCarModeIcon = getDrawable(ctx, R.drawable.ic_sysbar_back_carmode);
-        mHomeCarModeIcon = getDrawable(ctx, R.drawable.ic_sysbar_home_carmode);
-    }
-
     private void reloadNavIcons() {
-        updateIcons(mContext, Configuration.EMPTY, mConfiguration);
+        updateIcons(Configuration.EMPTY, mConfiguration);
     }
 
-    private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
-        int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
-        int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
-        Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
-        Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
-
+    private void updateIcons(Configuration oldConfig, Configuration newConfig) {
         final boolean orientationChange = oldConfig.orientation != newConfig.orientation;
         final boolean densityChange = oldConfig.densityDpi != newConfig.densityDpi;
         final boolean dirChange = oldConfig.getLayoutDirection() != newConfig.getLayoutDirection();
 
         if (orientationChange || densityChange) {
-            mDockedIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_docked);
-            mHomeDefaultIcon = getHomeDrawable(lightContext, darkContext);
+            mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked);
+            mHomeDefaultIcon = getHomeDrawable();
         }
         if (densityChange || dirChange) {
-            mRecentIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_recent);
-            mMenuIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_menu);
+            mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
+            mMenuIcon = getDrawable(R.drawable.ic_sysbar_menu);
 
-            mAccessibilityIcon = getDrawable(lightContext, darkContext,
-                    R.drawable.ic_sysbar_accessibility_button, false /* hasShadow */);
-
-            mImeIcon = getDrawable(lightContext, darkContext, R.drawable.ic_ime_switcher_default,
+            mAccessibilityIcon = getDrawable(R.drawable.ic_sysbar_accessibility_button,
                     false /* hasShadow */);
 
-            updateRotateSuggestionButtonStyle(mRotateBtnStyle, false);
+            mImeIcon = getDrawable(R.drawable.ic_ime_switcher_default, false /* hasShadow */);
 
-            if (ALTERNATE_CAR_MODE_UI) {
-                updateCarModeIcons(ctx);
-            }
+            updateRotateSuggestionButtonStyle(mRotateBtnStyle, false);
         }
         if (orientationChange || densityChange || dirChange) {
-            mBackIcon = getBackDrawable(lightContext, darkContext);
+            mBackIcon = getBackDrawable();
         }
     }
 
-    public KeyButtonDrawable getBackDrawable(Context lightContext, Context darkContext) {
-        KeyButtonDrawable drawable = chooseNavigationIconDrawable(lightContext, darkContext,
-                R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_quick_step);
+    public KeyButtonDrawable getBackDrawable() {
+        KeyButtonDrawable drawable = chooseNavigationIconDrawable(R.drawable.ic_sysbar_back,
+                R.drawable.ic_sysbar_back_quick_step);
         orientBackButton(drawable);
         return drawable;
     }
 
-    public KeyButtonDrawable getHomeDrawable(Context lightContext, Context darkContext) {
+    public KeyButtonDrawable getHomeDrawable() {
         final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
         KeyButtonDrawable drawable = quickStepEnabled
-                ? getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_home_quick_step)
-                : getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_home);
+                ? getDrawable(R.drawable.ic_sysbar_home_quick_step)
+                : getDrawable(R.drawable.ic_sysbar_home);
         orientHomeButton(drawable);
         return drawable;
     }
@@ -549,33 +531,26 @@
         drawable.setRotation(mVertical ? 90 : 0);
     }
 
-    private KeyButtonDrawable chooseNavigationIconDrawable(Context lightContext,
-            Context darkContext, @DrawableRes int icon, @DrawableRes int quickStepIcon) {
+    private KeyButtonDrawable chooseNavigationIconDrawable(@DrawableRes int icon,
+            @DrawableRes int quickStepIcon) {
         final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
-        return quickStepEnabled
-                ? getDrawable(lightContext, darkContext, quickStepIcon)
-                : getDrawable(lightContext, darkContext, icon);
+        return quickStepEnabled ? getDrawable(quickStepIcon) : getDrawable(icon);
     }
 
-    private KeyButtonDrawable getDrawable(Context lightContext, Context darkContext,
-            @DrawableRes int icon) {
-        return getDrawable(lightContext, darkContext, icon, true /* hasShadow */);
+    private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
+        return getDrawable(mContext, icon, true /* hasShadow */);
     }
 
-    private KeyButtonDrawable getDrawable(Context lightContext, Context darkContext,
-            @DrawableRes int icon, boolean hasShadow) {
-        return KeyButtonDrawable.create(lightContext, lightContext.getDrawable(icon),
-                darkContext.getDrawable(icon), hasShadow);
+    private KeyButtonDrawable getDrawable(@DrawableRes int icon, boolean hasShadow) {
+        return getDrawable(mContext, icon, hasShadow);
     }
 
-    private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon) {
-        // Legacy image icons using a single image will not support shadows
-        return KeyButtonDrawable.create(ctx, ctx.getDrawable(icon), null, false /* hasShadow */);
-    }
-
-    private TintedKeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon,
-            @ColorInt int lightColor, @ColorInt int darkColor) {
-        return TintedKeyButtonDrawable.create(ctx.getDrawable(icon), lightColor, darkColor);
+    private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon, boolean hasShadow) {
+        final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
+        final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
+        Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
+        Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
+        return KeyButtonDrawable.create(lightContext, darkContext, icon, hasShadow);
     }
 
     @Override
@@ -585,10 +560,6 @@
         super.setLayoutDirection(layoutDirection);
     }
 
-    private KeyButtonDrawable getBackIcon(boolean carMode) {
-        return carMode ? mBackCarModeIcon : mBackIcon;
-    }
-
     public void setNavigationIconHints(int hints) {
         if (hints == mNavigationIconHints) return;
         final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
@@ -626,9 +597,9 @@
         // to recent icon is not required.
         final boolean useAltBack =
                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
-        KeyButtonDrawable backIcon = getBackIcon(mUseCarModeUi);
+        KeyButtonDrawable backIcon = mBackIcon;
         orientBackButton(backIcon);
-        KeyButtonDrawable homeIcon = mUseCarModeUi ? mHomeCarModeIcon : mHomeDefaultIcon;
+        KeyButtonDrawable homeIcon = mHomeDefaultIcon;
         if (!mUseCarModeUi) {
             orientHomeButton(homeIcon);
         }
@@ -845,31 +816,20 @@
         mRotateBtnStyle = style;
         final Context ctx = getContext();
 
-        // Extract the dark and light tints
-        final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
-        final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
-        Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);
-        Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
-        final int lightColor = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor);
-        final int darkColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
-
         // Use the supplied style to set the icon's rotation parameters
         Context rotateContext = new ContextThemeWrapper(ctx, style);
 
         // Recreate the icon and set it if needed
-        TintedKeyButtonDrawable priorIcon = mRotateSuggestionIcon;
+        float previousIntensity = mRotateSuggestionIcon != null
+                ? mRotateSuggestionIcon.getDarkIntensity() : 0;
         mRotateSuggestionIcon = getDrawable(rotateContext, R.drawable.ic_sysbar_rotate_button,
-                lightColor, darkColor);
-
-        // Apply any prior set dark intensity
-        if (priorIcon != null && priorIcon.isDarkIntensitySet()) {
-            mRotateSuggestionIcon.setDarkIntensity(priorIcon.getDarkIntensity());
-        }
+                false /* hasShadow */);
+        mRotateSuggestionIcon.setDarkIntensity(previousIntensity);
 
         if (setIcon) getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
     }
 
-    public int setRotateButtonVisibility(final boolean visible) {
+    public int setRotateButtonVisibility(boolean visible) {
         // Never show if a11y is visible
         final boolean adjVisible = visible && !mShowAccessibilityButton;
         final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE;
@@ -881,13 +841,9 @@
         mShowRotateButton = visible;
 
         // Stop any active animations if hidden
-        if (!visible) {
-            Drawable d = mRotateSuggestionIcon.getDrawable(0);
-            if (d instanceof AnimatedVectorDrawable) {
-                AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d;
-                avd.clearAnimationCallbacks();
-                avd.reset();
-            }
+        if (!visible && mRotateSuggestionIcon.canAnimate()) {
+            mRotateSuggestionIcon.clearAnimationCallbacks();
+            mRotateSuggestionIcon.resetAnimation();
         }
 
         // Hide/restore other button visibility, if necessary
@@ -1082,7 +1038,7 @@
         super.onConfigurationChanged(newConfig);
         boolean uiCarModeChanged = updateCarMode(newConfig);
         updateTaskSwitchHelper();
-        updateIcons(getContext(), mConfiguration, newConfig);
+        updateIcons(mConfiguration, newConfig);
         updateRecentsIcon();
         mRecentsOnboarding.onConfigurationChanged(newConfig);
         if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadowKeyDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadowKeyDrawable.java
deleted file mode 100644
index 8bd8048..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadowKeyDrawable.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-
-import android.graphics.Bitmap;
-import android.graphics.BlurMaskFilter;
-import android.graphics.BlurMaskFilter.Blur;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-/**
- * A drawable which adds shadow around a child drawable.
- */
-public class ShadowKeyDrawable extends Drawable {
-    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-
-    private final ShadowDrawableState mState;
-
-    public ShadowKeyDrawable(Drawable d) {
-        this(d, new ShadowDrawableState());
-    }
-
-    private ShadowKeyDrawable(Drawable d, ShadowDrawableState state) {
-        mState = state;
-        if (d != null) {
-            mState.mBaseHeight = d.getIntrinsicHeight();
-            mState.mBaseWidth = d.getIntrinsicWidth();
-            mState.mChangingConfigurations = d.getChangingConfigurations();
-            mState.mChildState = d.getConstantState();
-        }
-    }
-
-    public void setRotation(float degrees) {
-        if (mState.mRotateDegrees != degrees) {
-            mState.mRotateDegrees = degrees;
-            mState.mLastDrawnBitmap = null;
-            invalidateSelf();
-        }
-    }
-
-    public void setTranslationX(float x) {
-        setTranslation(x, mState.mTranslationY);
-    }
-
-    public void setTranslationY(float y) {
-        setTranslation(mState.mTranslationX, y);
-    }
-
-    public void setTranslation(float x, float y) {
-        if (mState.mTranslationX != x || mState.mTranslationY != y) {
-            mState.mTranslationX = x;
-            mState.mTranslationY = y;
-            mState.mLastDrawnBitmap = null;
-            invalidateSelf();
-        }
-    }
-
-    public void setShadowProperties(int x, int y, int size, int color) {
-        if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y
-                || mState.mShadowSize != size || mState.mShadowColor != color) {
-            mState.mShadowOffsetX = x;
-            mState.mShadowOffsetY = y;
-            mState.mShadowSize = size;
-            mState.mShadowColor = color;
-            mState.mLastDrawnBitmap = null;
-            invalidateSelf();
-        }
-    }
-
-    public float getRotation() {
-        return mState.mRotateDegrees;
-    }
-
-    public float getTranslationX() {
-        return mState.mTranslationX;
-    }
-
-    public float getTranslationY() {
-        return mState.mTranslationY;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        Rect bounds = getBounds();
-        if (bounds.isEmpty()) {
-            return;
-        }
-
-        // If no cache or previous cached bitmap is hardware/software acceleration does not match
-        // the current canvas on draw then regenerate
-        if (mState.mLastDrawnBitmap == null
-                || mState.mIsHardwareBitmap != canvas.isHardwareAccelerated()) {
-            mState.mIsHardwareBitmap = canvas.isHardwareAccelerated();
-            regenerateBitmapCache();
-        }
-        canvas.drawBitmap(mState.mLastDrawnBitmap, null, bounds, mPaint);
-    }
-
-    @Override
-    public void setTint(int tintColor) {
-        super.setTint(tintColor);
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mPaint.setAlpha(alpha);
-        invalidateSelf();
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-        mPaint.setColorFilter(colorFilter);
-        invalidateSelf();
-    }
-
-    @Override
-    public ConstantState getConstantState() {
-        return mState;
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mState.mBaseHeight;
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mState.mBaseWidth;
-    }
-
-    @Override
-    public boolean canApplyTheme() {
-        return mState.canApplyTheme();
-    }
-
-    private void regenerateBitmapCache() {
-        final int width = getIntrinsicWidth();
-        final int height = getIntrinsicHeight();
-        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
-        canvas.save();
-        final float radians = (float) (mState.mRotateDegrees * Math.PI / 180);
-
-        // Rotate canvas before drawing original drawable if no shadow
-        if (mState.mShadowSize == 0) {
-            canvas.rotate(mState.mRotateDegrees, width / 2, height / 2);
-        }
-
-        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
-        final Drawable d = mState.mChildState.newDrawable().mutate();
-        d.setBounds(0, 0, mState.mBaseWidth, mState.mBaseHeight);
-        canvas.translate(mState.mTranslationX, mState.mTranslationY);
-        d.draw(canvas);
-
-        if (mState.mShadowSize > 0) {
-            // Draws the shadow
-            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-            paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL));
-            int[] offset = new int[2];
-
-            final Bitmap shadow = bitmap.extractAlpha(paint, offset);
-
-            paint.setMaskFilter(null);
-            paint.setColor(mState.mShadowColor);
-            bitmap.eraseColor(Color.TRANSPARENT);
-
-            canvas.rotate(mState.mRotateDegrees, width / 2, height / 2);
-
-            final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY
-                    + Math.cos(radians) * mState.mShadowOffsetX) - mState.mTranslationX;
-            final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY
-                    - Math.sin(radians) * mState.mShadowOffsetX) - mState.mTranslationY;
-
-            canvas.drawBitmap(shadow, offset[0] + shadowOffsetX, offset[1] + shadowOffsetY, paint);
-            d.draw(canvas);
-        }
-
-        if (mState.mIsHardwareBitmap) {
-            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
-        }
-
-        mState.mLastDrawnBitmap = bitmap;
-        canvas.restore();
-    }
-
-    private static class ShadowDrawableState extends ConstantState {
-        int mChangingConfigurations;
-        int mBaseWidth;
-        int mBaseHeight;
-        float mRotateDegrees;
-        float mTranslationX;
-        float mTranslationY;
-        int mShadowOffsetX;
-        int mShadowOffsetY;
-        int mShadowSize;
-        int mShadowColor;
-
-        boolean mIsHardwareBitmap;
-        Bitmap mLastDrawnBitmap;
-        ConstantState mChildState;
-
-        @Override
-        public Drawable newDrawable() {
-            return new ShadowKeyDrawable(null, this);
-        }
-
-        @Override
-        public int getChangingConfigurations() {
-            return mChangingConfigurations;
-        }
-
-        @Override
-        public boolean canApplyTheme() {
-            return true;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 8e31f31..945d9b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -16,23 +16,34 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.annotation.Nullable;
+import android.animation.ArgbEvaluator;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
 import android.util.FloatProperty;
-import android.view.Gravity;
-
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ShadowKeyDrawable;
 
 /**
- * Drawable for {@link KeyButtonView}s which contains an asset for both normal mode and light
- * navigation bar mode.
+ * Drawable for {@link KeyButtonView}s that supports tinting between two colors, rotation and shows
+ * a shadow. AnimatedVectorDrawable will only support tinting from intensities but has no support
+ * for shadows nor rotations.
  */
-public class KeyButtonDrawable extends LayerDrawable {
+public class KeyButtonDrawable extends Drawable {
 
     public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_ROTATE =
         new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") {
@@ -60,83 +71,343 @@
             }
         };
 
-    private final boolean mHasDarkDrawable;
+    private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final ShadowDrawableState mState;
+    private AnimatedVectorDrawable mAnimatedDrawable;
 
-    public static KeyButtonDrawable create(Context lightContext, Drawable lightDrawable,
-            @Nullable Drawable darkDrawable, boolean hasShadow) {
-        if (darkDrawable != null) {
-            ShadowKeyDrawable light = new ShadowKeyDrawable(lightDrawable.mutate());
-            ShadowKeyDrawable dark = new ShadowKeyDrawable(darkDrawable.mutate());
-            if (hasShadow) {
-                // Only apply the shadow on the light drawable
-                Resources res = lightContext.getResources();
-                int offsetX = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_x);
-                int offsetY = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_y);
-                int radius = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_radius);
-                int color = lightContext.getColor(R.color.nav_key_button_shadow_color);
-                light.setShadowProperties(offsetX, offsetY, radius, color);
-            }
-            return new KeyButtonDrawable(new Drawable[] { light, dark });
-        } else {
-            return new KeyButtonDrawable(new Drawable[] {
-                    new ShadowKeyDrawable(lightDrawable.mutate()) });
-        }
+    public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor) {
+        this(d, new ShadowDrawableState(lightColor, darkColor,
+                d instanceof AnimatedVectorDrawable));
     }
 
-    protected KeyButtonDrawable(Drawable[] drawables) {
-        super(drawables);
-        for (int i = 0; i < drawables.length; i++) {
-            setLayerGravity(i, Gravity.CENTER);
+    private KeyButtonDrawable(Drawable d, ShadowDrawableState state) {
+        mState = state;
+        if (d != null) {
+            mState.mBaseHeight = d.getIntrinsicHeight();
+            mState.mBaseWidth = d.getIntrinsicWidth();
+            mState.mChangingConfigurations = d.getChangingConfigurations();
+            mState.mChildState = d.getConstantState();
         }
-        mutate();
-        mHasDarkDrawable = drawables.length > 1;
-        setDarkIntensity(0f);
+        if (canAnimate()) {
+            mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate();
+            setDrawableBounds(mAnimatedDrawable);
+        }
     }
 
     public void setDarkIntensity(float intensity) {
-        if (!mHasDarkDrawable) {
-            return;
-        }
-        getDrawable(0).setAlpha((int) ((1 - intensity) * 255f));
-        getDrawable(1).setAlpha((int) (intensity * 255f));
-        invalidateSelf();
+        mState.mDarkIntensity = intensity;
+        final int color = (int) ArgbEvaluator.getInstance()
+                .evaluate(intensity, mState.mLightColor, mState.mDarkColor);
+        updateShadowAlpha();
+        setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_ATOP));
     }
 
     public void setRotation(float degrees) {
-        if (getDrawable(0) instanceof ShadowKeyDrawable) {
-            ((ShadowKeyDrawable) getDrawable(0)).setRotation(degrees);
+        if (canAnimate()) {
+            // AnimatedVectorDrawables will not support rotation
+            return;
         }
-        if (mHasDarkDrawable && getDrawable(1) instanceof ShadowKeyDrawable) {
-            ((ShadowKeyDrawable) getDrawable(1)).setRotation(degrees);
+        if (mState.mRotateDegrees != degrees) {
+            mState.mRotateDegrees = degrees;
+            invalidateSelf();
         }
     }
 
+    public void setTranslationX(float x) {
+        setTranslation(x, mState.mTranslationY);
+    }
+
     public void setTranslationY(float y) {
-        if (getDrawable(0) instanceof ShadowKeyDrawable) {
-            ((ShadowKeyDrawable) getDrawable(0)).setTranslationY(y);
+        setTranslation(mState.mTranslationX, y);
+    }
+
+    public void setTranslation(float x, float y) {
+        if (mState.mTranslationX != x || mState.mTranslationY != y) {
+            mState.mTranslationX = x;
+            mState.mTranslationY = y;
+            invalidateSelf();
         }
-        if (mHasDarkDrawable && getDrawable(1) instanceof ShadowKeyDrawable) {
-            ((ShadowKeyDrawable) getDrawable(1)).setTranslationY(y);
+    }
+
+    public void setShadowProperties(int x, int y, int size, int color) {
+        if (canAnimate()) {
+            // AnimatedVectorDrawables will not support shadows
+            return;
         }
+        if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y
+                || mState.mShadowSize != size || mState.mShadowColor != color) {
+            mState.mShadowOffsetX = x;
+            mState.mShadowOffsetY = y;
+            mState.mShadowSize = size;
+            mState.mShadowColor = color;
+            mShadowPaint.setColorFilter(
+                    new PorterDuffColorFilter(mState.mShadowColor, Mode.SRC_ATOP));
+            updateShadowAlpha();
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mState.mAlpha = alpha;
+        mIconPaint.setAlpha(alpha);
+        updateShadowAlpha();
+        invalidateSelf();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mIconPaint.setColorFilter(colorFilter);
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.setColorFilter(colorFilter);
+        }
+        invalidateSelf();
+    }
+
+    public float getDarkIntensity() {
+        return mState.mDarkIntensity;
     }
 
     public float getRotation() {
-        if (getDrawable(0) instanceof ShadowKeyDrawable) {
-            return ((ShadowKeyDrawable) getDrawable(0)).getRotation();
-        }
-        if (mHasDarkDrawable && getDrawable(1) instanceof ShadowKeyDrawable) {
-            return ((ShadowKeyDrawable) getDrawable(1)).getRotation();
-        }
-        return 0;
+        return mState.mRotateDegrees;
+    }
+
+    public float getTranslationX() {
+        return mState.mTranslationX;
     }
 
     public float getTranslationY() {
-        if (getDrawable(0) instanceof ShadowKeyDrawable) {
-            return ((ShadowKeyDrawable) getDrawable(0)).getTranslationY();
+        return mState.mTranslationY;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        return mState;
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mState.mBaseHeight + (mState.mShadowSize + Math.abs(mState.mShadowOffsetY)) * 2;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mState.mBaseWidth + (mState.mShadowSize + Math.abs(mState.mShadowOffsetX)) * 2;
+    }
+
+    public boolean canAnimate() {
+        return mState.mSupportsAnimation;
+    }
+
+    public void startAnimation() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.start();
         }
-        if (mHasDarkDrawable && getDrawable(1) instanceof ShadowKeyDrawable) {
-            return ((ShadowKeyDrawable) getDrawable(1)).getTranslationY();
+    }
+
+    public void resetAnimation() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.reset();
         }
-        return 0;
+    }
+
+    public void clearAnimationCallbacks() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.clearAnimationCallbacks();
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        Rect bounds = getBounds();
+        if (bounds.isEmpty()) {
+            return;
+        }
+
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.draw(canvas);
+        } else {
+            // If no cache or previous cached bitmap is hardware/software acceleration does not
+            // match the current canvas on draw then regenerate
+            boolean hwBitmapChanged = mState.mIsHardwareBitmap != canvas.isHardwareAccelerated();
+            if (hwBitmapChanged) {
+                mState.mIsHardwareBitmap = canvas.isHardwareAccelerated();
+            }
+            if (mState.mLastDrawnIcon == null || hwBitmapChanged) {
+                regenerateBitmapIconCache();
+            }
+            canvas.save();
+            canvas.translate(mState.mTranslationX, mState.mTranslationY);
+            canvas.rotate(mState.mRotateDegrees, getIntrinsicWidth() / 2, getIntrinsicHeight() / 2);
+
+            if (mState.mShadowSize > 0) {
+                if (mState.mLastDrawnShadow == null || hwBitmapChanged) {
+                    regenerateBitmapShadowCache();
+                }
+
+                // Translate (with rotation offset) before drawing the shadow
+                final float radians = (float) (mState.mRotateDegrees * Math.PI / 180);
+                final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY
+                        + Math.cos(radians) * mState.mShadowOffsetX) - mState.mTranslationX;
+                final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY
+                        - Math.sin(radians) * mState.mShadowOffsetX) - mState.mTranslationY;
+                canvas.drawBitmap(mState.mLastDrawnShadow, shadowOffsetX, shadowOffsetY,
+                        mShadowPaint);
+            }
+            canvas.drawBitmap(mState.mLastDrawnIcon, null, bounds, mIconPaint);
+            canvas.restore();
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mState.canApplyTheme();
+    }
+
+    private void regenerateBitmapIconCache() {
+        final int width = getIntrinsicWidth();
+        final int height = getIntrinsicHeight();
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+
+        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
+        final Drawable d = mState.mChildState.newDrawable().mutate();
+        setDrawableBounds(d);
+        d.draw(canvas);
+
+        if (mState.mIsHardwareBitmap) {
+            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
+        }
+        mState.mLastDrawnIcon = bitmap;
+    }
+
+    private void regenerateBitmapShadowCache() {
+        if (mState.mShadowSize == 0) {
+            // No shadow
+            mState.mLastDrawnIcon = null;
+            return;
+        }
+
+        final int width = getIntrinsicWidth();
+        final int height = getIntrinsicHeight();
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
+        final Drawable d = mState.mChildState.newDrawable().mutate();
+        setDrawableBounds(d);
+        d.draw(canvas);
+
+        // Draws the shadow from original drawable
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL));
+        int[] offset = new int[2];
+        final Bitmap shadow = bitmap.extractAlpha(paint, offset);
+        paint.setMaskFilter(null);
+        bitmap.eraseColor(Color.TRANSPARENT);
+        canvas.drawBitmap(shadow, offset[0], offset[1], paint);
+
+        if (mState.mIsHardwareBitmap) {
+            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
+        }
+        mState.mLastDrawnShadow = bitmap;
+    }
+
+    /**
+     * Set the alpha of the shadow. As dark intensity increases, drop the alpha of the shadow since
+     * dark color and shadow should not be visible at the same time.
+     */
+    private void updateShadowAlpha() {
+        // Update the color from the original color's alpha as the max
+        int alpha = Color.alpha(mState.mShadowColor);
+        mShadowPaint.setAlpha(
+                Math.round(alpha * (mState.mAlpha / 255f) * (1 - mState.mDarkIntensity)));
+    }
+
+    /**
+     * Prevent shadow clipping by offsetting the drawable bounds by the shadow and its offset
+     * @param d the drawable to set the bounds
+     */
+    private void setDrawableBounds(Drawable d) {
+        final int offsetX = mState.mShadowSize + Math.abs(mState.mShadowOffsetX);
+        final int offsetY = mState.mShadowSize + Math.abs(mState.mShadowOffsetY);
+        d.setBounds(offsetX, offsetY, getIntrinsicWidth() - offsetX,
+                getIntrinsicHeight() - offsetY);
+    }
+
+    private static class ShadowDrawableState extends ConstantState {
+        int mChangingConfigurations;
+        int mBaseWidth;
+        int mBaseHeight;
+        float mRotateDegrees;
+        float mTranslationX;
+        float mTranslationY;
+        int mShadowOffsetX;
+        int mShadowOffsetY;
+        int mShadowSize;
+        int mShadowColor;
+        float mDarkIntensity;
+        int mAlpha;
+
+        boolean mIsHardwareBitmap;
+        Bitmap mLastDrawnIcon;
+        Bitmap mLastDrawnShadow;
+        ConstantState mChildState;
+
+        final int mLightColor;
+        final int mDarkColor;
+        final boolean mSupportsAnimation;
+
+        public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor,
+                boolean animated) {
+            mLightColor = lightColor;
+            mDarkColor = darkColor;
+            mSupportsAnimation = animated;
+            mAlpha = 255;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new KeyButtonDrawable(null, this);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return mChangingConfigurations;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return true;
+        }
+    }
+
+    public static KeyButtonDrawable create(Context lightContext, Context darkContext,
+        @DrawableRes int iconResId, boolean hasShadow) {
+        return create(lightContext,
+            Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor),
+            Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor),
+            iconResId, hasShadow);
+    }
+
+    public static KeyButtonDrawable create(Context context, @ColorInt int lightColor,
+        @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow) {
+        final KeyButtonDrawable drawable = new KeyButtonDrawable(context.getDrawable(iconResId),
+            lightColor, darkColor);
+        if (hasShadow) {
+            final Resources res = context.getResources();
+            int offsetX = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_x);
+            int offsetY = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_y);
+            int radius = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_radius);
+            int color = context.getColor(R.color.nav_key_button_shadow_color);
+            drawable.setShadowProperties(offsetX, offsetY, radius, color);
+        }
+        return drawable;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java
deleted file mode 100644
index 30a5cc84..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.annotation.ColorInt;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.systemui.statusbar.phone.ShadowKeyDrawable;
-
-/**
- * Drawable for {@link KeyButtonView}s which contains a single asset and colors for light and dark
- * navigation bar mode.
- */
-public class TintedKeyButtonDrawable extends KeyButtonDrawable {
-
-    private final int mLightColor;
-    private final int mDarkColor;
-
-    public static final float DARK_INTENSITY_NOT_SET = -1f;
-    private float mDarkIntensity = DARK_INTENSITY_NOT_SET;
-
-    public static TintedKeyButtonDrawable create(Drawable drawable, @ColorInt int lightColor,
-            @ColorInt int darkColor) {
-        return new TintedKeyButtonDrawable(new Drawable[] { drawable },
-                lightColor, darkColor);
-    }
-
-    private TintedKeyButtonDrawable(Drawable[] drawables, int lightColor, int darkColor){
-        super(drawables);
-        mLightColor = lightColor;
-        mDarkColor = darkColor;
-        setDarkIntensity(0f); // Set initial coloration
-    }
-
-    @Override
-    public void setDarkIntensity(float intensity) {
-        // Duplicate intensity scaling from KeyButtonDrawable
-        mDarkIntensity = intensity;
-
-        // Dark and light colors may have an alpha component
-        final int intermediateColor = ColorUtils.compositeColors(
-                blendAlpha(mDarkColor, intensity),
-                blendAlpha(mLightColor, (1f - intensity)));
-
-        getDrawable(0).setTint(intermediateColor);
-        invalidateSelf();
-    }
-
-    private int blendAlpha(int color, float alpha) {
-        final float newAlpha = alpha < 0f ? 0f : (alpha > 1f ? 1f : alpha);
-        final float colorAlpha = Color.alpha(color) / 255f;
-        final int alphaInt = (int) (255 * newAlpha * colorAlpha); // Blend by multiplying
-        // Ensure alpha is clamped [0-255] or ColorUtils will crash
-        return ColorUtils.setAlphaComponent(color, alphaInt);
-    }
-
-    public boolean isDarkIntensitySet() {
-        return mDarkIntensity != DARK_INTENSITY_NOT_SET;
-    }
-
-    public float getDarkIntensity() {
-        return mDarkIntensity;
-    }
-}