From ca3c17d773aeebc482a7d9f1d29340868b26ecca Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Fri, 29 Dec 2023 23:47:38 +0100 Subject: [PATCH] implement DrawableContainer and copy StateListDrawable from AOSP --- meson.build | 1 + ...roid_graphics_drawable_DrawableContainer.h | 29 ++ ...roid_graphics_drawable_DrawableContainer.c | 43 +++ .../android/content/res/Resources.java | 9 +- .../android/graphics/drawable/Drawable.java | 10 +- .../graphics/drawable/DrawableContainer.java | 58 +++- .../graphics/drawable/StateListDrawable.java | 315 ++++++++++++++++-- src/api-impl/android/view/View.java | 27 +- src/api-impl/com/android/internal/R.java | 27 ++ 9 files changed, 482 insertions(+), 37 deletions(-) create mode 100644 src/api-impl-jni/generated_headers/android_graphics_drawable_DrawableContainer.h create mode 100644 src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.c diff --git a/meson.build b/meson.build index 876325a7..2b636080 100644 --- a/meson.build +++ b/meson.build @@ -81,6 +81,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/database/android_database_SQLiteConnection.c', 'src/api-impl-jni/graphics/android_graphics_BitmapFactory.c', 'src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c', + 'src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.c', 'src/api-impl-jni/graphics/android_graphics_Typeface.c', 'src/api-impl-jni/media/android_media_MediaCodec.c', 'src/api-impl-jni/android_content_res_AssetManager.c', diff --git a/src/api-impl-jni/generated_headers/android_graphics_drawable_DrawableContainer.h b/src/api-impl-jni/generated_headers/android_graphics_drawable_DrawableContainer.h new file mode 100644 index 00000000..74898d31 --- /dev/null +++ b/src/api-impl-jni/generated_headers/android_graphics_drawable_DrawableContainer.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class android_graphics_drawable_DrawableContainer */ + +#ifndef _Included_android_graphics_drawable_DrawableContainer +#define _Included_android_graphics_drawable_DrawableContainer +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: android_graphics_drawable_DrawableContainer + * Method: native_constructor + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_android_graphics_drawable_DrawableContainer_native_1constructor + (JNIEnv *, jobject); + +/* + * Class: android_graphics_drawable_DrawableContainer + * Method: native_selectChild + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_android_graphics_drawable_DrawableContainer_native_1selectChild + (JNIEnv *, jobject, jlong, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.c b/src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.c new file mode 100644 index 00000000..20444e4e --- /dev/null +++ b/src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.c @@ -0,0 +1,43 @@ +#include + +#include "../defines.h" +#include "../generated_headers/android_graphics_drawable_DrawableContainer.h" + +struct _ContainerPaintable { + GObject parent_instance; + GdkPaintable *child; +}; +G_DECLARE_FINAL_TYPE(ContainerPaintable, container_paintable, CONTAINER, PAINTABLE, GObject) + +static void container_paintable_snapshot(GdkPaintable *paintable, GdkSnapshot *snapshot, double width, double height) { + ContainerPaintable *container = CONTAINER_PAINTABLE(paintable); + if (container->child) + gdk_paintable_snapshot(container->child, snapshot, width, height); +} + +static void container_paintable_init(ContainerPaintable *container_paintable) +{ +} + +static void container_paintable_paintable_init(GdkPaintableInterface *iface) +{ + iface->snapshot = container_paintable_snapshot; +} + +static void container_paintable_class_init(ContainerPaintableClass *class) { +} + +G_DEFINE_TYPE_WITH_CODE(ContainerPaintable, container_paintable, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GDK_TYPE_PAINTABLE, container_paintable_paintable_init)) + +JNIEXPORT jlong JNICALL Java_android_graphics_drawable_DrawableContainer_native_1constructor(JNIEnv *env, jobject this) +{ + return _INTPTR(g_object_new(container_paintable_get_type(), NULL)); +} + +JNIEXPORT void JNICALL Java_android_graphics_drawable_DrawableContainer_native_1selectChild(JNIEnv *env, jobject this, jlong ptr, jlong child_ptr) +{ + ContainerPaintable *container = CONTAINER_PAINTABLE(_PTR(ptr)); + container->child = GDK_PAINTABLE(_PTR(child_ptr)); + gdk_paintable_invalidate_contents(GDK_PAINTABLE(container)); +} diff --git a/src/api-impl/android/content/res/Resources.java b/src/api-impl/android/content/res/Resources.java index 7d762621..de36ec37 100644 --- a/src/api-impl/android/content/res/Resources.java +++ b/src/api-impl/android/content/res/Resources.java @@ -41,6 +41,7 @@ import com.reandroid.arsc.value.ValueItem; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; +import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -1490,12 +1491,12 @@ public class Resources { // To support generic XML files we will need to manually parse // out the attributes from the XML file (applying type information // contained in the resources and such). - XmlBlock.Parser parser = (XmlBlock.Parser)set; - mAssets.retrieveAttributes(parser.mParseState, attrs, - array.mData, array.mIndices); + ResXmlPullParser parser = (ResXmlPullParser)set; + mAssets.applyStyle(Collections.EMPTY_MAP, 0, 0, + set, attrs, array.mData, array.mIndices); array.mRsrcs = attrs; - // array.mXml = parser; + array.mXml = parser; return array; } diff --git a/src/api-impl/android/graphics/drawable/Drawable.java b/src/api-impl/android/graphics/drawable/Drawable.java index a281290e..ef1dbf11 100644 --- a/src/api-impl/android/graphics/drawable/Drawable.java +++ b/src/api-impl/android/graphics/drawable/Drawable.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -59,7 +60,10 @@ public class Drawable { public void draw(Canvas canvas) {} public boolean setState(int[] stateSet) { - this.mStateSet = stateSet; + if (!Arrays.equals(this.mStateSet, stateSet)) { + this.mStateSet = stateSet; + return onStateChange(stateSet); + } return false; } @@ -138,5 +142,9 @@ public class Drawable { return new Drawable(paintable); } + protected boolean onStateChange(int[] stateSet) { + return false; + } + protected static native long native_paintable_from_path(String path); } diff --git a/src/api-impl/android/graphics/drawable/DrawableContainer.java b/src/api-impl/android/graphics/drawable/DrawableContainer.java index 83fb8943..e48241cc 100644 --- a/src/api-impl/android/graphics/drawable/DrawableContainer.java +++ b/src/api-impl/android/graphics/drawable/DrawableContainer.java @@ -1,13 +1,61 @@ package android.graphics.drawable; -import android.graphics.Canvas; +import android.content.res.Resources; public class DrawableContainer extends Drawable { - @Override - public void draw(Canvas canvas) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'draw'"); + private DrawableContainerState state; + private int curIndex = -1; + + protected native long native_constructor(); + protected native void native_selectChild(long container, long child); + + public DrawableContainer() { + paintable = native_constructor(); + } + + public boolean selectDrawable(int idx) { + if (idx >= 0 && idx < state.childCount && idx != curIndex && state.drawables[idx] != null) { + curIndex = idx; + native_selectChild(paintable, state.drawables[idx].paintable); + return true; + } + return false; + } + + protected void setConstantState(DrawableContainerState state) { + this.state = state; + } + + public static class DrawableContainerState { + + private Drawable drawables[] = new Drawable[10]; + private int childCount = 0; + + public DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res) { + } + + public int getCapacity() { + return drawables.length; + } + + public int getChildCount() { + return childCount; + } + + public int addChild(Drawable dr) { + if (childCount >= drawables.length) { + growArray(drawables.length, drawables.length * 2); + } + drawables[childCount] = dr; + return childCount++; + } + + public void growArray(int oldSize, int newSize) { + Drawable[] newDrawables = new Drawable[newSize]; + System.arraycopy(drawables, 0, newDrawables, 0, oldSize); + drawables = newDrawables; + } } } diff --git a/src/api-impl/android/graphics/drawable/StateListDrawable.java b/src/api-impl/android/graphics/drawable/StateListDrawable.java index a2ce1417..aad192f7 100644 --- a/src/api-impl/android/graphics/drawable/StateListDrawable.java +++ b/src/api-impl/android/graphics/drawable/StateListDrawable.java @@ -1,35 +1,308 @@ +/* + * Copyright (C) 2006 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 java.io.IOException; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.StateSet; + +import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import android.content.res.Resources; -import android.content.res.Resources.Theme; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.Slog; +import java.io.IOException; +import java.util.Arrays; -public class StateListDrawable extends Drawable { +/** + * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string + * ID value. + *

+ *

It can be defined in an XML file with the <selector> element. + * Each state Drawable is defined in a nested <item> element. For more + * information, see the guide to Drawable Resources.

+ * + * @attr ref android.R.styleable#StateListDrawable_visible + * @attr ref android.R.styleable#StateListDrawable_variablePadding + * @attr ref android.R.styleable#StateListDrawable_constantSize + * @attr ref android.R.styleable#DrawableStates_state_focused + * @attr ref android.R.styleable#DrawableStates_state_window_focused + * @attr ref android.R.styleable#DrawableStates_state_enabled + * @attr ref android.R.styleable#DrawableStates_state_checkable + * @attr ref android.R.styleable#DrawableStates_state_checked + * @attr ref android.R.styleable#DrawableStates_state_selected + * @attr ref android.R.styleable#DrawableStates_state_activated + * @attr ref android.R.styleable#DrawableStates_state_active + * @attr ref android.R.styleable#DrawableStates_state_single + * @attr ref android.R.styleable#DrawableStates_state_first + * @attr ref android.R.styleable#DrawableStates_state_middle + * @attr ref android.R.styleable#DrawableStates_state_last + * @attr ref android.R.styleable#DrawableStates_state_pressed + */ +public class StateListDrawable extends DrawableContainer { + private static final String TAG = "StateListDrawable"; - @Override - public void draw(Canvas canvas) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'draw'"); + private static final boolean DEBUG = false; + + private StateListState mStateListState; + private boolean mMutated; + + public StateListDrawable() { + this(null, null); } - public void addState(int[] stateSet, Drawable drawable) {} + /** + * Add a new image/string ID to the set of images. + * + * @param stateSet An array of resource Ids to associate with the image. + * Switch to this image by calling setState(). + * @param drawable The image to show. Note this must be a unique Drawable that is not shared + * between any other View or Drawable otherwise the results are + * undefined and can lead to unexpected rendering behavior + */ + public void addState(int[] stateSet, Drawable drawable) { + if (drawable != null) { + mStateListState.addStateSet(stateSet, drawable); + // in case the new state matches our current state... + onStateChange(getState()); + } + } - public void inflate(Resources resources, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - while (parser.next() != XmlPullParser.END_DOCUMENT) { - if ("item".equals(parser.getName())) { - int resid = attrs.getAttributeResourceValue("http://schemas.android.com/apk/res/android", "drawable", -1); - Drawable drawable = resources.getDrawable(resid, theme); - this.paintable = drawable.paintable; - Slog.i("StateListDrawable", "item resid = " + resid + ", paintable = " + drawable.paintable); + @Override + public boolean isStateful() { + return true; + } + + @Override + protected boolean onStateChange(int[] stateSet) { + final boolean changed = super.onStateChange(stateSet); + + int idx = mStateListState.indexOfStateSet(stateSet); + if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states " + + Arrays.toString(stateSet) + " found " + idx); + if (idx < 0) { + idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); + } + + return selectDrawable(idx) || changed; + } + + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = r.obtainAttributes(attrs, R.styleable.StateListDrawable); + a.recycle(); + + inflateChildElements(r, parser, attrs, theme); + + onStateChange(getState()); + } + + /** + * Inflates child elements from XML. + */ + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + final StateListState state = mStateListState; + final int innerDepth = parser.getDepth() + 1; + int type; + int depth; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth + || type != XmlPullParser.END_TAG)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + if (depth > innerDepth || !parser.getName().equals("item")) { + continue; + } + + // This allows state list drawable item elements to be themed at + // inflation time but does NOT make them work for Zygote preload. + final TypedArray a = r.obtainAttributes(attrs, + R.styleable.StateListDrawableItem); + Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable); + a.recycle(); + + final int[] states = extractStateSet(attrs); + + // Loading child elements modifies the state of the AttributeSet's + // underlying parser, so it needs to happen after obtaining + // attributes and extracting states. + if (dr == null) { + while ((type = parser.next()) == XmlPullParser.TEXT) { + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException( + parser.getPositionDescription() + + ": tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + // dr = Drawable.createFromXmlInner(r, parser, attrs, theme); + } + + state.addStateSet(states, dr); + } + } + + /** + * Extracts state_ attributes from an attribute set. + * + * @param attrs The attribute set. + * @return An array of state_ attributes. + */ + int[] extractStateSet(AttributeSet attrs) { + int j = 0; + final int numAttrs = attrs.getAttributeCount(); + int[] states = new int[numAttrs]; + for (int i = 0; i < numAttrs; i++) { + final int stateResId = attrs.getAttributeNameResource(i); + switch (stateResId) { + case 0: + break; + case R.attr.drawable: + case R.attr.id: + // Ignore attributes from StateListDrawableItem and + // AnimatedStateListDrawableItem. + continue; + default: + states[j++] = attrs.getAttributeBooleanValue(i, false) + ? stateResId : -stateResId; } } - } - + states = StateSet.trimStateSet(states, j); + return states; + } + + StateListState getStateListState() { + return mStateListState; + } + + /** + * Gets the state set at an index. + * + * @param index The index of the state set. + * @return The state set at the index. + * @see #getStateCount() + * @see #getStateDrawable(int) + */ + public int[] getStateSet(int index) { + return mStateListState.mStateSets[index]; + } + + /** + * Gets the index of the drawable with the provided state set. + * + * @param stateSet the state set to look up + * @return the index of the provided state set, or -1 if not found + * @see #getStateDrawable(int) + * @see #getStateSet(int) + */ + public int findStateDrawableIndex(int[] stateSet) { + return mStateListState.indexOfStateSet(stateSet); + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mStateListState.mutate(); + mMutated = true; + } + return this; + } + + static class StateListState extends DrawableContainerState { + int[] mThemeAttrs; + int[][] mStateSets; + + StateListState(StateListState orig, StateListDrawable owner, Resources res) { + super(orig, owner, res); + + if (orig != null) { + // Perform a shallow copy and rely on mutate() to deep-copy. + mThemeAttrs = orig.mThemeAttrs; + mStateSets = orig.mStateSets; + } else { + mThemeAttrs = null; + mStateSets = new int[getCapacity()][]; + } + } + + void mutate() { + mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null; + + final int[][] stateSets = new int[mStateSets.length][]; + for (int i = mStateSets.length - 1; i >= 0; i--) { + stateSets[i] = mStateSets[i] != null ? mStateSets[i].clone() : null; + } + mStateSets = stateSets; + } + + int addStateSet(int[] stateSet, Drawable drawable) { + final int pos = addChild(drawable); + mStateSets[pos] = stateSet; + return pos; + } + + int indexOfStateSet(int[] stateSet) { + final int[][] stateSets = mStateSets; + final int N = getChildCount(); + for (int i = 0; i < N; i++) { + if (StateSet.stateSetMatches(stateSets[i], stateSet)) { + return i; + } + } + return -1; + } + + @Override + public void growArray(int oldSize, int newSize) { + super.growArray(oldSize, newSize); + final int[][] newStateSets = new int[newSize][]; + System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize); + mStateSets = newStateSets; + } + } + + protected void setConstantState(DrawableContainerState state) { + super.setConstantState(state); + + if (state instanceof StateListState) { + mStateListState = (StateListState) state; + } + } + + private StateListDrawable(StateListState state, Resources res) { + // Every state list drawable has its own constant state. + final StateListState newState = new StateListState(state, this, res); + setConstantState(newState); + onStateChange(getState()); + } + + /** + * This constructor exists so subclasses can avoid calling the default + * constructor and setting up a StateListDrawable-specific constant state. + */ + StateListDrawable(StateListState state) { + if (state != null) { + setConstantState(state); + } + } } diff --git a/src/api-impl/android/view/View.java b/src/api-impl/android/view/View.java index 7f9deee5..661fc701 100644 --- a/src/api-impl/android/view/View.java +++ b/src/api-impl/android/view/View.java @@ -1,5 +1,6 @@ package android.view; +import android.R; import android.animation.StateListAnimator; import android.content.Context; import android.content.res.ColorStateList; @@ -818,6 +819,8 @@ public class View extends Object { private int visibility = View.VISIBLE; private float alpha = 1.0f; + private boolean pressed = false; + private Drawable background; public View(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -973,6 +976,11 @@ public class View extends Object { public void setPressed(boolean pressed) { Slog.w(TAG, "calling setPressed on " + this + " with value: " + pressed); + if (this.pressed != pressed) { + this.pressed = pressed; + if (background != null && background.isStateful()) + background.setState(getDrawableState()); + } } public void setSelected(boolean selected) { @@ -1108,10 +1116,7 @@ public class View extends Object { public static class AccessibilityDelegate {} public Drawable getBackground() { - return new Drawable() { - @Override - public void draw(Canvas canvas) {} - }; + return background; } public void setClickable(boolean clickable) {} @@ -1245,6 +1250,7 @@ public class View extends Object { } public void setBackgroundDrawable(Drawable backgroundDrawable) { + this.background = backgroundDrawable; native_setBackgroundDrawable(widget, backgroundDrawable != null ? backgroundDrawable.paintable : 0); } @@ -1321,7 +1327,14 @@ public class View extends Object { } } - public final int[] getDrawableState() {return new int[0];} + public final int[] getDrawableState() { + int[] state = new int[2]; + state[0] = R.attr.state_enabled; + if (pressed) { + state[1] = R.attr.state_pressed; + } + return state; + } public float getRotation() {return 0.f;} @@ -1452,7 +1465,9 @@ public class View extends Object { public int getLayoutDirection() {return LAYOUT_DIRECTION_LTR;} - public void setBackground(Drawable background) {} + public void setBackground(Drawable background) { + setBackgroundDrawable(background); + } private float elevation; public void setElevation(float elevation) { diff --git a/src/api-impl/com/android/internal/R.java b/src/api-impl/com/android/internal/R.java index 490199b3..db3599b9 100644 --- a/src/api-impl/com/android/internal/R.java +++ b/src/api-impl/com/android/internal/R.java @@ -43662,6 +43662,33 @@ public final class R { @attr name android:visible */ public static final int StateListDrawable_visible = 1; + /** Represents a single state inside a StateListDrawable. +

Includes the following attributes:

+ + + + + +
AttributeDescription
{@link #StateListDrawableItem_drawable android:drawable} Reference to a drawable resource to use for the state.
+ @see #StateListDrawableItem_drawable + */ + public static final int[] StateListDrawableItem = { + 0x01010199 + }; + /** +

+ @attr description + Reference to a drawable resource to use for the state. If not + given, the drawable must be defined by the first child tag. + + +

Must be a reference to another resource, in the form "@[+][package:]type:name" + or to a theme attribute in the form "?[package:][type:]name". +

This corresponds to the global attribute + resource symbol {@link android.R.attr#drawable}. + @attr name android:drawable + */ + public static final int StateListDrawableItem_drawable = 0; /** Attributes that can be used with a Storage.

Includes the following attributes: