Create ColorStateListDrawable

Previously, you could not use references to colors in places that
expected a Drawable. This new class, ColorStateListDrawable bridges the
gap by attaching a ColorStateList to a ColorDrawable, and hooks it into
resource loading wherever you might expect a Drawable.

Fixes: 18126411
Test: Unit tests to be added to CTS
Change-Id: I0ff9a089669da96e6b22b214f04d6726bc278152
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 19e399a..b644e23 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1,17 +1,17 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright 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
+ * 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
+ *      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.
+ * 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 android.content.res;
 
@@ -35,6 +35,7 @@
 import android.graphics.ImageDecoder;
 import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.ColorStateListDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableContainer;
 import android.icu.text.PluralRules;
@@ -846,10 +847,15 @@
             stack.push(id);
             try {
                 if (file.endsWith(".xml")) {
-                    final XmlResourceParser rp = loadXmlResourceParser(
-                            file, id, value.assetCookie, "drawable");
-                    dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);
-                    rp.close();
+                    if (file.startsWith("res/color/")) {
+                        ColorStateList csl = loadColorStateList(wrapper, value, id, null);
+                        dr = (csl != null ? new ColorStateListDrawable(csl) : null);
+                    } else {
+                        final XmlResourceParser rp = loadXmlResourceParser(
+                                file, id, value.assetCookie, "drawable");
+                        dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);
+                        rp.close();
+                    }
                 } else {
                     final InputStream is = mAssets.openNonAsset(
                             value.assetCookie, file, AssetManager.ACCESS_STREAMING);
diff --git a/graphics/java/android/graphics/drawable/ColorStateListDrawable.java b/graphics/java/android/graphics/drawable/ColorStateListDrawable.java
new file mode 100644
index 0000000..40ba5d1
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/ColorStateListDrawable.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 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 android.graphics.drawable;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.content.pm.ActivityInfo;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.util.MathUtils;
+
+/**
+ * A Drawable that manages a {@link ColorDrawable} to make it stateful and backed by a
+ * {@link ColorStateList}.
+ */
+public class ColorStateListDrawable extends Drawable implements Drawable.Callback {
+    private ColorDrawable mColorDrawable;
+    private ColorStateListDrawableState mState;
+
+    public ColorStateListDrawable() {
+        mState = new ColorStateListDrawableState();
+        initializeColorDrawable();
+    }
+
+    public ColorStateListDrawable(ColorStateList colorStateList) {
+        mState = new ColorStateListDrawableState();
+        initializeColorDrawable();
+        setColorStateList(colorStateList);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mColorDrawable.draw(canvas);
+    }
+
+    @Override
+    @IntRange(from = 0, to = 255)
+    public int getAlpha() {
+        return mColorDrawable.getAlpha();
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mState.isStateful();
+    }
+
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return mState.hasFocusStateSpecified();
+    }
+
+    @Override
+    public @NonNull Drawable getCurrent() {
+        return mColorDrawable;
+    }
+
+    @Override
+    public void applyTheme(Resources.Theme t) {
+        super.applyTheme(t);
+
+        if (mState.mColor != null) {
+            setColorStateList(mState.mColor.obtainForTheme(t));
+        }
+
+        if (mState.mTint != null) {
+            setTintList(mState.mTint.obtainForTheme(t));
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return super.canApplyTheme() || mState.canApplyTheme();
+    }
+
+    @Override
+    public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
+        mState.mAlpha = alpha;
+        onStateChange(getState());
+    }
+
+    /**
+     * Remove the alpha override, reverting to the alpha defined on each color in the
+     * {@link ColorStateList}.
+     */
+    public void clearAlpha() {
+        mState.mAlpha = -1;
+        onStateChange(getState());
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        mState.mTint = tint;
+        mColorDrawable.setTintList(tint);
+        onStateChange(getState());
+    }
+
+    @Override
+    public void setTintMode(PorterDuff.Mode tintMode) {
+        mState.mTintMode = tintMode;
+        mColorDrawable.setTintMode(tintMode);
+        onStateChange(getState());
+    }
+
+    @Override
+    public ColorFilter getColorFilter() {
+        return mColorDrawable.getColorFilter();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mColorDrawable.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public @PixelFormat.Opacity int getOpacity() {
+        return mColorDrawable.getOpacity();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        if (mState.mColor != null) {
+            int color = mState.mColor.getColorForState(state, mState.mColor.getDefaultColor());
+
+            if (mState.mAlpha != -1) {
+                color = color & 0xFFFFFF | MathUtils.constrain(mState.mAlpha, 0, 255) << 24;
+            }
+
+            if (color != mColorDrawable.getColor()) {
+                mColorDrawable.setColor(color);
+                mColorDrawable.setState(state);
+                return true;
+            } else {
+                return mColorDrawable.setState(state);
+            }
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void invalidateDrawable(Drawable who) {
+        final Callback callback = getCallback();
+
+        if (callback != null) {
+            callback.invalidateDrawable(who);
+        }
+    }
+
+    @Override
+    public void scheduleDrawable(Drawable who, Runnable what, long when) {
+        final Callback callback = getCallback();
+
+        if (callback != null) {
+            callback.scheduleDrawable(who, what, when);
+        }
+    }
+
+    @Override
+    public void unscheduleDrawable(Drawable who, Runnable what) {
+        final Callback callback = getCallback();
+
+        if (callback != null) {
+            callback.unscheduleDrawable(who, what);
+        }
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        return mState;
+    }
+
+    /**
+     * Returns the ColorStateList backing this Drawable, or a new ColorStateList of the default
+     * ColorDrawable color if one hasn't been defined yet.
+     *
+     * @return a ColorStateList
+     */
+    public @NonNull ColorStateList getColorStateList() {
+        if (mState.mColor == null) {
+            return ColorStateList.valueOf(mColorDrawable.getColor());
+        } else {
+            return mState.mColor;
+        }
+    }
+
+    /**
+     * Replace this Drawable's ColorStateList. It is not copied, so changes will propagate on the
+     * next call to {@link #setState(int[])}.
+     *
+     * @param colorStateList A color state list to attach.
+     */
+    public void setColorStateList(ColorStateList colorStateList) {
+        mState.mColor = colorStateList;
+        onStateChange(getState());
+    }
+
+    static final class ColorStateListDrawableState extends ConstantState {
+        ColorStateList mColor = null;
+        ColorStateList mTint = null;
+        int mAlpha = -1;
+        PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+        @ActivityInfo.Config int mChangingConfigurations = 0;
+
+        ColorStateListDrawableState() {
+        }
+
+
+        @Override
+        public Drawable newDrawable() {
+            return new ColorStateListDrawable(this);
+        }
+
+        @Override
+        public @ActivityInfo.Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | (mColor != null ? mColor.getChangingConfigurations() : 0)
+                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
+        }
+
+        public boolean isStateful() {
+            return (mColor != null && mColor.isStateful())
+                    || (mTint != null && mTint.isStateful());
+        }
+
+        public boolean hasFocusStateSpecified() {
+            return (mColor != null && mColor.hasFocusStateSpecified())
+                    || (mTint != null && mTint.hasFocusStateSpecified());
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return (mColor != null && mColor.canApplyTheme())
+                    || (mTint != null && mTint.canApplyTheme());
+        }
+    }
+
+    private ColorStateListDrawable(ColorStateListDrawableState state) {
+        mState = state;
+        initializeColorDrawable();
+    }
+
+    private void initializeColorDrawable() {
+        mColorDrawable = new ColorDrawable();
+        mColorDrawable.setCallback(this);
+
+        if (mState.mTint != null) {
+            mColorDrawable.setTintList(mState.mTint);
+        }
+
+        if (mState.mTintMode != DEFAULT_TINT_MODE) {
+            mColorDrawable.setTintMode(mState.mTintMode);
+        }
+    }
+}