Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 54999da4 authored by Romain Guy's avatar Romain Guy
Browse files

Remove MipmapDrawable

It's a hidden API that nobody uses.

Change-Id: Ie0ecbc45cd191a595e53fe8765defe6f2aa7e3f6
parent c648ed73
Loading
Loading
Loading
Loading
+0 −4
Original line number Diff line number Diff line
@@ -4057,10 +4057,6 @@
        <attr name="drawable" />
    </declare-styleable>

    <declare-styleable name="MipmapDrawableItem">
        <attr name="drawable" />
    </declare-styleable>

    <!-- Drawable used to rotate another drawable. -->
    <declare-styleable name="RotateDrawable">
        <attr name="visible" />
+0 −4
Original line number Diff line number Diff line
@@ -869,10 +869,6 @@ public abstract class Drawable {
            drawable = new StateListDrawable();
        } else if (name.equals("level-list")) {
            drawable = new LevelListDrawable();
        /* Probably not doing this.
        } else if (name.equals("mipmap")) {
            drawable = new MipmapDrawable();
        */
        } else if (name.equals("layer-list")) {
            drawable = new LayerDrawable();
        } else if (name.equals("transition")) {
+0 −312
Original line number Diff line number Diff line
/*
 * 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 org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;

import java.io.IOException;

/**
 * @hide -- we are probably moving to do MipMaps in another way (more integrated
 * with the resource system).
 *
 * A resource that manages a number of alternate Drawables, and which actually draws the one which
 * size matches the most closely the drawing bounds. Providing several pre-scaled version of the
 * drawable helps minimizing the aliasing artifacts that can be introduced by the scaling.
 *
 * <p>
 * Use {@link #addDrawable(Drawable)} to define the different Drawables that will represent the
 * mipmap levels of this MipmapDrawable. The mipmap Drawable that will actually be used when this
 * MipmapDrawable is drawn is the one which has the smallest intrinsic height greater or equal than
 * the bounds' height. This selection ensures that the best available mipmap level is scaled down to
 * draw this MipmapDrawable.
 * </p>
 *
 * If the bounds' height is larger than the largest mipmap, the largest mipmap will be scaled up.
 * Note that Drawables without intrinsic height (i.e. with a negative value, such as Color) will
 * only be used if no other mipmap Drawable are provided. The Drawables' intrinsic heights should
 * not be changed after the Drawable has been added to this MipmapDrawable.
 *
 * <p>
 * The different mipmaps' parameters (opacity, padding, color filter, gravity...) should typically
 * be similar to ensure a continuous visual appearance when the MipmapDrawable is scaled. The aspect
 * ratio of the different mipmaps should especially be equal.
 * </p>
 *
 * A typical example use of a MipmapDrawable would be for an image which is intended to be scaled at
 * various sizes, and for which one wants to provide pre-scaled versions to precisely control its
 * appearance.
 *
 * <p>
 * The intrinsic size of a MipmapDrawable are inferred from those of the largest mipmap (in terms of
 * {@link Drawable#getIntrinsicHeight()}). On the opposite, its minimum
 * size is defined by the smallest provided mipmap.
 * </p>

 * It can be defined in an XML file with the <code>&lt;mipmap></code> element.
 * Each mipmap Drawable is defined in a nested <code>&lt;item></code>. For example:
 * <pre>
 * &lt;mipmap xmlns:android="http://schemas.android.com/apk/res/android">
 *  &lt;item android:drawable="@drawable/my_image_8" />
 *  &lt;item android:drawable="@drawable/my_image_32" />
 *  &lt;item android:drawable="@drawable/my_image_128" />
 * &lt;/mipmap>
 *</pre>
 * <p>
 * With this XML saved into the res/drawable/ folder of the project, it can be referenced as
 * the drawable for an {@link android.widget.ImageView}. Assuming that the heights of the provided
 * drawables are respectively 8, 32 and 128 pixels, the first one will be scaled down when the
 * bounds' height is lower or equal than 8 pixels. The second drawable will then be used up to a
 * height of 32 pixels and the largest drawable will be used for greater heights.
 * </p>
 * @attr ref android.R.styleable#MipmapDrawableItem_drawable
 */
public class MipmapDrawable extends DrawableContainer {
    private final MipmapContainerState mMipmapContainerState;
    private boolean mMutated;

    public MipmapDrawable() {
        this(null, null);
    }

    /**
     * Adds a Drawable to the list of available mipmap Drawables. The Drawable actually used when
     * this MipmapDrawable is drawn is determined from its bounds.
     *
     * This method has no effect if drawable is null.
     *
     * @param drawable The Drawable that will be added to list of available mipmap Drawables.
     */

    public void addDrawable(Drawable drawable) {
        if (drawable != null) {
            mMipmapContainerState.addDrawable(drawable);
            onDrawableAdded();
        }
    }

    private void onDrawableAdded() {
        // selectDrawable assumes that the container content does not change.
        // When a Drawable is added, the same index can correspond to a new Drawable, and since
        // selectDrawable has a fast exit case when oldIndex==newIndex, the new drawable could end
        // up not being used in place of the previous one if they happen to share the same index.
        // This make sure the new computed index can actually replace the previous one.
        selectDrawable(-1);
        onBoundsChange(getBounds());
    }

    // overrides from Drawable

    @Override
    protected void onBoundsChange(Rect bounds) {
        final int index = mMipmapContainerState.indexForBounds(bounds);

        // Will call invalidateSelf() if needed
        selectDrawable(index);

        super.onBoundsChange(bounds);
    }

    @Override
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
    throws XmlPullParserException, IOException {

        super.inflate(r, parser, attrs);

        int type;

        final int innerDepth = parser.getDepth() + 1;
        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;
            }

            TypedArray a = r.obtainAttributes(attrs,
                    com.android.internal.R.styleable.MipmapDrawableItem);

            int drawableRes = a.getResourceId(
                    com.android.internal.R.styleable.MipmapDrawableItem_drawable, 0);

            a.recycle();

            Drawable dr;
            if (drawableRes != 0) {
                dr = r.getDrawable(drawableRes);
            } else {
                while ((type = parser.next()) == XmlPullParser.TEXT) {
                }
                if (type != XmlPullParser.START_TAG) {
                    throw new XmlPullParserException(
                            parser.getPositionDescription()
                            + ": <item> tag requires a 'drawable' attribute or "
                            + "child tag defining a drawable");
                }
                dr = Drawable.createFromXmlInner(r, parser, attrs);
            }

            mMipmapContainerState.addDrawable(dr);
        }

        onDrawableAdded();
    }

    @Override
    public Drawable mutate() {
        if (!mMutated && super.mutate() == this) {
            mMipmapContainerState.mMipmapHeights = mMipmapContainerState.mMipmapHeights.clone();
            mMutated = true;
        }
        return this;
    }

    private final static class MipmapContainerState extends DrawableContainerState {
        private int[] mMipmapHeights;

        MipmapContainerState(MipmapContainerState orig, MipmapDrawable owner, Resources res) {
            super(orig, owner, res);

            if (orig != null) {
                mMipmapHeights = orig.mMipmapHeights;
            } else {
                mMipmapHeights = new int[getChildren().length];
            }

            // Change the default value
            setConstantSize(true);
        }

        /**
         * Returns the index of the child mipmap drawable that will best fit the provided bounds.
         * This index is determined by comparing bounds' height and children intrinsic heights.
         * The returned mipmap index is the smallest mipmap which height is greater or equal than
         * the bounds' height. If the bounds' height is larger than the largest mipmap, the largest
         * mipmap index is returned.
         *
         * @param bounds The bounds of the MipMapDrawable.
         * @return The index of the child Drawable that will best fit these bounds, or -1 if there
         * are no children mipmaps.
         */
        public int indexForBounds(Rect bounds) {
            final int boundsHeight = bounds.height();
            final int N = getChildCount();
            for (int i = 0; i < N; i++) {
                if (boundsHeight <= mMipmapHeights[i]) {
                    return i;
                }
            }

            // No mipmap larger than bounds found. Use largest one which will be scaled up.
            if (N > 0) {
                return N - 1;
            }
            // No Drawable mipmap at all
            return -1;
        }

        /**
         * Adds a Drawable to the list of available mipmap Drawables. This list can be retrieved
         * using {@link DrawableContainer.DrawableContainerState#getChildren()} and this method
         * ensures that it is always sorted by increasing {@link Drawable#getIntrinsicHeight()}.
         *
         * @param drawable The Drawable that will be added to children list
         */
        public void addDrawable(Drawable drawable) {
            // Insert drawable in last position, correctly resetting cached values and
            // especially mComputedConstantSize
            int pos = addChild(drawable);

            // Bubble sort the last drawable to restore the sort by intrinsic height
            final int drawableHeight = drawable.getIntrinsicHeight();

            while (pos > 0) {
                final Drawable previousDrawable = mDrawables[pos-1];
                final int previousIntrinsicHeight = previousDrawable.getIntrinsicHeight();

                if (drawableHeight < previousIntrinsicHeight) {
                    mDrawables[pos] = previousDrawable;
                    mMipmapHeights[pos] = previousIntrinsicHeight;

                    mDrawables[pos-1] = drawable;
                    mMipmapHeights[pos-1] = drawableHeight;
                    pos--;
                } else {
                    break;
                }
            }
        }

        /**
         * Intrinsic sizes are those of the largest available mipmap.
         * Minimum sizes are those of the smallest available mipmap.
         */
        @Override
        protected void computeConstantSize() {
            final int N = getChildCount();
            if (N > 0) {
                final Drawable smallestDrawable = mDrawables[0];
                mConstantMinimumWidth = smallestDrawable.getMinimumWidth();
                mConstantMinimumHeight = smallestDrawable.getMinimumHeight();

                final Drawable largestDrawable = mDrawables[N-1];
                mConstantWidth = largestDrawable.getIntrinsicWidth();
                mConstantHeight = largestDrawable.getIntrinsicHeight();
            } else {
                mConstantWidth = mConstantHeight = -1;
                mConstantMinimumWidth = mConstantMinimumHeight = 0;
            }
            mComputedConstantSize = true;
        }

        @Override
        public Drawable newDrawable() {
            return new MipmapDrawable(this, null);
        }

        @Override
        public Drawable newDrawable(Resources res) {
            return new MipmapDrawable(this, res);
        }

        @Override
        public void growArray(int oldSize, int newSize) {
            super.growArray(oldSize, newSize);
            int[] newInts = new int[newSize];
            System.arraycopy(mMipmapHeights, 0, newInts, 0, oldSize);
            mMipmapHeights = newInts;
        }
    }

    private MipmapDrawable(MipmapContainerState state, Resources res) {
        MipmapContainerState as = new MipmapContainerState(state, this, res);
        mMipmapContainerState = as;
        setConstantState(as);
        onDrawableAdded();
    }
}
+0 −280
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 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.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.MipmapDrawable;
import android.graphics.drawable.DrawableContainer.DrawableContainerState;
import android.test.InstrumentationTestCase;

public class MipmapDrawableTest extends InstrumentationTestCase {
    private MockMipmapDrawable mMipmapDrawable;

    private DrawableContainerState mDrawableContainerState;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mMipmapDrawable = new MockMipmapDrawable();
        mDrawableContainerState = (DrawableContainerState) mMipmapDrawable.getConstantState();
    }

    public void testMipmapDrawable() {
        new MipmapDrawable();
        // Check the values set in the constructor
        assertNotNull(new MipmapDrawable().getConstantState());
        assertTrue(new MockMipmapDrawable().hasCalledOnBoundsChanged());
    }

    public void testAddDrawable() {
        assertEquals(0, mDrawableContainerState.getChildCount());

        // nothing happens if drawable is null
        mMipmapDrawable.reset();
        mMipmapDrawable.addDrawable(null);
        assertEquals(0, mDrawableContainerState.getChildCount());
        assertFalse(mMipmapDrawable.hasCalledOnBoundsChanged());

        mMipmapDrawable.reset();
        mMipmapDrawable.addDrawable(new MockDrawable());
        assertEquals(1, mDrawableContainerState.getChildCount());
        assertTrue(mMipmapDrawable.hasCalledOnBoundsChanged());

        mMipmapDrawable.reset();
        mMipmapDrawable.addDrawable(new MockDrawable());
        assertEquals(2, mDrawableContainerState.getChildCount());
        assertTrue(mMipmapDrawable.hasCalledOnBoundsChanged());
    }

    public void testSortedByHeight() {
        Drawable small = new MockDrawable(8);
        Drawable medium = new MockDrawable(32);
        Drawable large = new MockDrawable(128);

        mMipmapDrawable.addDrawable(medium);
        assertSame(medium, mDrawableContainerState.getChildren()[0]);

        mMipmapDrawable.addDrawable(small);
        assertSame(small, mDrawableContainerState.getChildren()[0]);
        assertSame(medium, mDrawableContainerState.getChildren()[1]);

        mMipmapDrawable.addDrawable(large);
        assertSame(small, mDrawableContainerState.getChildren()[0]);
        assertSame(medium, mDrawableContainerState.getChildren()[1]);
        assertSame(large, mDrawableContainerState.getChildren()[2]);

        mMipmapDrawable.addDrawable(small);
        assertSame(small, mDrawableContainerState.getChildren()[0]);
        assertSame(small, mDrawableContainerState.getChildren()[1]);
        assertSame(medium, mDrawableContainerState.getChildren()[2]);
        assertSame(large, mDrawableContainerState.getChildren()[3]);

        mMipmapDrawable.addDrawable(medium);
        assertSame(small, mDrawableContainerState.getChildren()[0]);
        assertSame(small, mDrawableContainerState.getChildren()[1]);
        assertSame(medium, mDrawableContainerState.getChildren()[2]);
        assertSame(medium, mDrawableContainerState.getChildren()[3]);
        assertSame(large, mDrawableContainerState.getChildren()[4]);

        mMipmapDrawable.addDrawable(large);
        assertSame(small, mDrawableContainerState.getChildren()[0]);
        assertSame(small, mDrawableContainerState.getChildren()[1]);
        assertSame(medium, mDrawableContainerState.getChildren()[2]);
        assertSame(medium, mDrawableContainerState.getChildren()[3]);
        assertSame(large, mDrawableContainerState.getChildren()[4]);
        assertSame(large, mDrawableContainerState.getChildren()[5]);
    }

    public void testSetBoundsOneItem() {
        // the method is not called if same bounds are set
        mMipmapDrawable.reset();
        mMipmapDrawable.setBounds(mMipmapDrawable.getBounds());
        assertFalse(mMipmapDrawable.hasCalledOnBoundsChanged());

        // the method is called if different bounds are set, even without drawables
        mMipmapDrawable.reset();
        mMipmapDrawable.setBounds(new Rect(0, 0, 0, mMipmapDrawable.getBounds().height() + 1));
        assertTrue(mMipmapDrawable.hasCalledOnBoundsChanged());

        // adding an item should check bounds to see if new drawable is more appropriate
        mMipmapDrawable.reset();
        Drawable item = new MockDrawable(42);
        mMipmapDrawable.addDrawable(item);
        assertTrue(mMipmapDrawable.hasCalledOnBoundsChanged());

        // the method is called if different bounds are set
        mMipmapDrawable.setBounds(new Rect(0, 0, 0, mMipmapDrawable.getBounds().height() + 1));
        assertTrue(mMipmapDrawable.hasCalledOnBoundsChanged());

        // check that correct drawable is selected for any size.
        mMipmapDrawable.setBounds(new Rect(0, 0, 0, item.getIntrinsicHeight() - 1));
        assertSame(item, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, item.getIntrinsicHeight()));
        assertSame(item, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, item.getIntrinsicHeight() + 1));
        assertSame(item, mMipmapDrawable.getCurrent());
    }

    public void testSetBounds() {
        Drawable small = new MockDrawable(8);
        Drawable medium = new MockDrawable(32);
        Drawable large = new MockDrawable(128);

        mMipmapDrawable.addDrawable(large);
        mMipmapDrawable.addDrawable(small);
        mMipmapDrawable.addDrawable(medium);

        // check that correct drawable is selected.
        mMipmapDrawable.setBounds(new Rect(0, 0, 0, small.getIntrinsicHeight() - 1));
        assertSame(small, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, small.getIntrinsicHeight()));
        assertSame(small, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, small.getIntrinsicHeight() + 1));
        assertSame(medium, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, medium.getIntrinsicHeight() - 1));
        assertSame(medium, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, medium.getIntrinsicHeight()));
        assertSame(medium, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, medium.getIntrinsicHeight() + 1));
        assertSame(large, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, large.getIntrinsicHeight() - 1));
        assertSame(large, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, large.getIntrinsicHeight()));
        assertSame(large, mMipmapDrawable.getCurrent());

        mMipmapDrawable.setBounds(new Rect(0, 0, 0, large.getIntrinsicHeight() + 1));
        assertSame(large, mMipmapDrawable.getCurrent());
    }

    public void testSizes() {
        // Check default value with no mipmap defined
        assertEquals(-1, mMipmapDrawable.getIntrinsicHeight());
        assertEquals(-1, mMipmapDrawable.getIntrinsicWidth());
        assertEquals(0, mMipmapDrawable.getMinimumHeight());
        assertEquals(0, mMipmapDrawable.getMinimumWidth());

        Drawable small = new MockDrawable(8, 4);
        Drawable medium = new MockDrawable(32, 16);
        Drawable large = new MockDrawable(128, 64);

        mMipmapDrawable.addDrawable(medium);
        assertEquals(medium.getIntrinsicHeight(), mMipmapDrawable.getIntrinsicHeight());
        assertEquals(medium.getMinimumHeight(), mMipmapDrawable.getMinimumHeight());

        mMipmapDrawable.addDrawable(large);
        assertEquals(large.getIntrinsicHeight(), mMipmapDrawable.getIntrinsicHeight());
        assertEquals(medium.getMinimumHeight(), mMipmapDrawable.getMinimumHeight());

        mMipmapDrawable.addDrawable(small);
        assertEquals(large.getIntrinsicHeight(), mMipmapDrawable.getIntrinsicHeight());
        assertEquals(small.getMinimumHeight(), mMipmapDrawable.getMinimumHeight());
    }

    public void testReplacementWhenAdded() {
        Drawable small = new MockDrawable(8);
        Drawable medium = new MockDrawable(32);
        Drawable large = new MockDrawable(128);

        // Small bounds, so that the smallest mipmap should always be selected
        mMipmapDrawable.setBounds(new Rect(0, 0, 0, 0));

        // Providing smaller versions, that should immediately be used as current
        mMipmapDrawable.addDrawable(large);
        assertSame(large, mMipmapDrawable.getCurrent());

        mMipmapDrawable.addDrawable(medium);
        assertSame(medium, mMipmapDrawable.getCurrent());

        mMipmapDrawable.addDrawable(small);
        assertSame(small, mMipmapDrawable.getCurrent());
    }

    private class MockMipmapDrawable extends MipmapDrawable {
        private boolean mHasCalledOnBoundsChanged;

        public boolean hasCalledOnBoundsChanged() {
            return mHasCalledOnBoundsChanged;
        }

        public void reset() {
            mHasCalledOnBoundsChanged = false;
        }

        @Override
        protected void onBoundsChange(Rect bounds) {
            super.onBoundsChange(bounds);
            mHasCalledOnBoundsChanged = true;
        }
    }

    private class MockDrawable extends Drawable {
        int mIntrinsicHeight;
        int mMinimumHeight;

        public MockDrawable() {
            this(0);
        }

        public MockDrawable(int intrinsicHeight) {
            this(intrinsicHeight, intrinsicHeight);
        }

        public MockDrawable(int intrinsicHeight, int minimumHeight) {
            mIntrinsicHeight = intrinsicHeight;
            mMinimumHeight = minimumHeight;
        }

        @Override
        public void draw(Canvas canvas) {
        }

        @Override
        public int getOpacity() {
            return 0;
        }

        @Override
        public void setAlpha(int alpha) {
        }

        @Override
        public void setColorFilter(ColorFilter cf) {
        }

        @Override
        public int getIntrinsicHeight() {
            return mIntrinsicHeight;
        }

        @Override
        public int getMinimumHeight() {
            return mMinimumHeight;
        }
    }
}