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

Commit 6d611a30 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Using clippath for adaptive icon drawable instead of bitmap shader

clipPath is already anti-aliased since Android S, so we do not
need to create extra bitmaps for this

Bug: 211896569
Bug: 238937089
Test: Updated tests / presubmit
Change-Id: I60ce42d91ca96babbde9faa4e5580b00f681de35
parent f800b3f5
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -204,6 +204,19 @@ public class AdaptiveIconDrawableTest extends AndroidTestCase {
        assertEquals(100, Color.alpha(bitmap.getPixel(50, 50)));
    }

    @Test
    public void testSetBounds() throws Exception {
        mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
        mIconDrawable.setBounds(0, 0, 100, 100);

        assertEquals(new Rect(-25, -25, 125, 125), mBackgroundDrawable.getBounds());
        assertEquals(new Rect(-25, -25, 125, 125), mForegroundDrawable.getBounds());

        mIconDrawable.setBounds(10, 10, 110, 110);
        assertEquals(new Rect(-15, -15, 135, 135), mBackgroundDrawable.getBounds());
        assertEquals(new Rect(-15, -15, 135, 135), mForegroundDrawable.getBounds());
    }

    //
    // Utils
    //
+45 −132
Original line number Diff line number Diff line
@@ -26,8 +26,6 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -39,8 +37,6 @@ import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.PathParser;
@@ -106,7 +102,8 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
    private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);

    /**
     * Clip path defined in R.string.config_icon_mask.
     * Unused path.
     * TODO: Remove once the layoutLib is updated
     */
    private static Path sMask;

@@ -114,9 +111,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
     * Scaled mask based on the view bounds.
     */
    private final Path mMask;
    private final Path mMaskScaleOnly;
    private final Path mMaskTransformed;
    private final Matrix mMaskMatrix;
    private final Region mTransparentRegion;
    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    /**
     * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
@@ -131,19 +129,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
     */
    LayerState mLayerState;

    private Shader mLayersShader;
    private Bitmap mLayersBitmap;

    private final Rect mTmpOutRect = new Rect();
    private Rect mHotspotBounds;
    private boolean mMutated;

    private boolean mSuspendChildInvalidation;
    private boolean mChildRequestedInvalidation;
    private final Canvas mCanvas;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
        Paint.FILTER_BITMAP_FLAG);

    /**
     * Constructor used for xml inflation.
     */
@@ -157,19 +146,16 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
     */
    AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
        mLayerState = createConstantState(state, res);
        // config_icon_mask from context bound resource may have been chaged using
        // config_icon_mask from context bound resource may have been changed using
        // OverlayManager. Read that one first.
        Resources r = ActivityThread.currentActivityThread() == null
                ? Resources.getSystem()
                : ActivityThread.currentActivityThread().getApplication().getResources();
        // TODO: either make sMask update only when config_icon_mask changes OR
        // get rid of it all-together in layoutlib
        sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
        mMask = new Path(sMask);
        mMaskScaleOnly = new Path(mMask);
        mMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
        mMaskTransformed = new Path();
        mMaskMatrix = new Matrix();
        mCanvas = new Canvas();
        mTransparentRegion = new Region();
        mPaint.setColor(Color.BLACK);
    }

    private ChildDrawable createChildDrawable(Drawable drawable) {
@@ -280,7 +266,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
     * @return the mask path object used to clip the drawable
     */
    public Path getIconMask() {
        return mMask;
        return mMaskTransformed;
    }

    /**
@@ -322,92 +308,47 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
        if (bounds.isEmpty()) {
            return;
        }
        updateLayerBounds(bounds);
    }

    private void updateLayerBounds(Rect bounds) {
        if (bounds.isEmpty()) {
            return;
        }
        try {
            suspendChildInvalidation();
            updateLayerBoundsInternal(bounds);
            updateMaskBoundsInternal(bounds);
        } finally {
            resumeChildInvalidation();
        }
    }

    /**
     * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE}
     */
    private void updateLayerBoundsInternal(Rect bounds) {
        int cX = bounds.width() / 2;
        int cY = bounds.height() / 2;
        // Set the child layer bounds bigger than the view port size
        // by {@link #DEFAULT_VIEW_PORT_SCALE}
        float cX = bounds.exactCenterX();
        float cY = bounds.exactCenterY();
        float insetWidth = bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2);
        float insetHeight = bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2);
        final Rect outRect = mTmpOutRect;
        outRect.set(
                (int) (cX - insetWidth),
                (int) (cY - insetHeight),
                (int) (cX + insetWidth),
                (int) (cY + insetHeight));

        for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
            final ChildDrawable r = mLayerState.mChildren[i];
            final Drawable d = r.mDrawable;
            if (d == null) {
                continue;
            }

            int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
            int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
            final Rect outRect = mTmpOutRect;
            outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);

            d.setBounds(outRect);
            if (r.mDrawable != null) {
                r.mDrawable.setBounds(outRect);
            }
        }

    private void updateMaskBoundsInternal(Rect b) {
        // reset everything that depends on the view bounds
        mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
        sMask.transform(mMaskMatrix, mMaskScaleOnly);

        mMaskMatrix.postTranslate(b.left, b.top);
        sMask.transform(mMaskMatrix, mMask);

        if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width()
                || mLayersBitmap.getHeight() != b.height()) {
            mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
        }
        // Update the clipping mask
        mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE);
        mMaskMatrix.postTranslate(bounds.left, bounds.top);
        mMask.transform(mMaskMatrix, mMaskTransformed);

        mPaint.setShader(null);
        // Clear the transparent region, it is calculated lazily
        mTransparentRegion.setEmpty();
        mLayersShader = null;
    }

    @Override
    public void draw(Canvas canvas) {
        if (mLayersBitmap == null) {
            return;
        }
        if (mLayersShader == null) {
            mCanvas.setBitmap(mLayersBitmap);
            mCanvas.drawColor(Color.BLACK);
        int saveCount = canvas.save();
        canvas.clipPath(mMaskTransformed);
        canvas.drawPaint(mPaint);
        if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
                mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas);
            mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(canvas);
        }
        if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
                mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas);
            }
            mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
            mPaint.setShader(mLayersShader);
            mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(canvas);
        }
        if (mMaskScaleOnly != null) {
            Rect bounds = getBounds();
            canvas.translate(bounds.left, bounds.top);
            canvas.drawPath(mMaskScaleOnly, mPaint);
            canvas.translate(-bounds.left, -bounds.top);
        }
    }

    @Override
    public void invalidateSelf() {
        mLayersShader = null;
        super.invalidateSelf();
        canvas.restoreToCount(saveCount);
    }

    @Override
@@ -600,38 +541,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
        return false;
    }

    /**
     * Temporarily suspends child invalidation.
     *
     * @see #resumeChildInvalidation()
     */
    private void suspendChildInvalidation() {
        mSuspendChildInvalidation = true;
    }

    /**
     * Resumes child invalidation after suspension, immediately performing an
     * invalidation if one was requested by a child during suspension.
     *
     * @see #suspendChildInvalidation()
     */
    private void resumeChildInvalidation() {
        mSuspendChildInvalidation = false;

        if (mChildRequestedInvalidation) {
            mChildRequestedInvalidation = false;
            invalidateSelf();
        }
    }

    @Override
    public void invalidateDrawable(@NonNull Drawable who) {
        if (mSuspendChildInvalidation) {
            mChildRequestedInvalidation = true;
        } else {
        invalidateSelf();
    }
    }

    @Override
    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
@@ -714,6 +627,13 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
        final ChildDrawable[] array = mLayerState.mChildren;
        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
            final Drawable dr = array[i].mDrawable;
            if (dr != null) {
                dr.setAlpha(alpha);
            }
        }
    }

    @Override
@@ -816,10 +736,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
            }
        }

        if (changed) {
            updateLayerBounds(getBounds());
        }

        return changed;
    }

@@ -835,10 +751,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
            }
        }

        if (changed) {
            updateLayerBounds(getBounds());
        }

        return changed;
    }

@@ -979,6 +891,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
        int mDensity;

        // The density to use when inflating/looking up the children drawables. A value of 0 means

        // use the system's density.
        int mSrcDensityOverride = 0;